3

What would be the best way, in delphi, to create and store data which will often be searched on and modified?

Basically, I would like to write a function that searches an existing database for telephone numbers and keeps track of how many times each telephone number has been used, the first date used, and the latest date used. The database that is being searched is basically a log of orders placed, containing the telephone number that was used to place the order. It's not an SQL database or anything that can easily be queried for such things (it's an old btrieve database), so I need to create a way of gaining this information (to eventually output to a text file).

I am thinking of creating a record containing the phone number, the two dates, and the number of times used, and then adding a record to a dynamic array for each telephone number. I would then search the array, entry by entry, for each record in the database, to see if the phone number for the current record is already in the array. Then updating or creating a record as necessary.

This seems like it would work, but as there are tens of thousands of entries in the database, it may not be the best way, and a rather slow and inefficient way of doing things. Is there a better way, given the limited actions I can perform on the database?

Someone suggested that rather than using an array, use a MySQL table to keep track of the numbers, and then query each number for every database record. This seems like even more overhead though!

Thanks a lot for your time.

4

5 回答 5

1

我将在完全断开的 TClientDataset(cds) 中注册聚合,并在您从循环中获取它们时更新这些值。如果 Btrieve 可以按电话号码排序,那就更好了。然后使用 cd 上的数据生成报告。

(如果你走这条路,我建议从Andreas Hausladen 的博客中获取Midas SpeedFix,以及你可以在那里找到的其他最好的东西)。

于 2009-05-05T16:19:07.650 回答
1

好的,这是一种运行良好并且应该可以很好地扩展的双通道老式方法(我曾经对数百万条记录数据库使用过这种方法,虽然需要时间但给出了准确的结果)。

  1. 下载并安装 Turbo Power SysTools——排序引擎非常适合这个过程。
  2. 创建一个排序,具有固定的电话号码记录,您将使用它进行排序。
  3. 遍历您的记录,在每个订单中,将电话号码添加到排序中。
  4. 第一次迭代完成后,开始从排序中弹出电话号码,如果电话号码与上次读取的电话号码相同,则增加计数器,否则报告号码并清除计数器。

这个过程也可以用任何 SQL 数据库完成,但我的经验是排序方法比管理临时表更快,并且生成相同的结果。

编辑——你说这是一个 BTrieve 数据库,为什么不直接在电话号码上创建一个键,对那个键进行排序,然后在这个表上应用第 4 步(下一个而不是弹出)。无论哪种方式,您都需要触摸数据库中的每条记录以获取计数,索引/排序只会使您的决策过程更容易。

例如,假设您有两个表,一个是 customer 表,一个是存储结果的表,另一个是 orders 表。按相同的电话号码对两者进行排序。然后在两个列表的顶部启动一个光标,然后应用以下伪代码:

Count := 0;
While (CustomerTable <> eof) and (OrderTable <> eof) do
  begin
    comp = comparetext( customer.phone, order.phone );
    while (comp = 0) and (not orderTable eof) do 
      begin
        inc( Count );
        order.next;
        comp = comparetext( customer.phone, order.phone );
      end;
    if comp < 0 then
      begin
        Customer.TotalCount = count;
        save customer;
        count := 0;
        Customer.next;
      end
    else if (Comp > 0) and (not OrderTable EOF) then
      begin
        Order.Next;  // order no customer
      end;  
   end;

// handle case where end of orders reached
if (OrdersTable EOF) and (not CustomersTable EOF) then
  begin
    Customer.TotalCount = count;
    save customer;
  end;

这段代码的好处是遍历两个列表一次。由于两个列表的排序相同,因此无需查找,仅在必要时才可以从上到下进行操作。唯一的要求是两个列表有共同点(在这个例子中是电话号码),并且两个列表都可以排序。

我没有处理有订单而没有客户的情况。我的假设是,如果没有客户,订单就不存在,并且会被跳过以进行计数。

于 2009-05-05T17:10:46.420 回答
0

如果您要将它保存在内存中并且不想要任何花哨的东西,那么您最好使用 TStringList 以便您可以使用 Find 函数。Find 使用 Hoare 的选择或快速选择,一个 O(n) 定位器。例如,定义一个类型:

type
   TPhoneData = class
      private
         fPhone:string;
         fFirstCalledDate:TDateTime;
         fLastCalledDate:TDateTime;
         fCallCount:integer;
      public
         constructor Create(phone:string; firstDate, lastDate:TDateTime);
         procedure updateCallData(date:TDateTime);
         property phoneNumber:string read fPhone write fPhone;
         property firstCalledDate:TDateTime read fFirstCalledDate write fFirstCalledDate;
         property lastCalledDate:TDateTime read fLastCalledDate write fLastCalledDate;
         property callCount:integer read fCallCount write fCallCount;
      end;

{ TPhoneData }

constructor TPhoneData.Create(phone: string; firstDate, lastDate: TDateTime);
begin
fCallCount:=1;
fFirstCalledDate:=firstDate;
fLastCalledDate:=lastDate;
fPhone:=phone;
end;

procedure TPhoneData.updateCallData(date: TDateTime);
begin
inc(fCallCount);
if fFirstCalledDate<date then fFirstCalledDate:=date;
if date>fLastCalledDate then fLastCalledDate:=date;
end;

然后填写,报告:

procedure TForm1.btnSortExampleClick(Sender: TObject);
const phoneSeed:array[0..9] of string = ('111-111-1111','222-222-2222','333-333-3333','444-444-4444','555-555-5555','666-666-6666','777-777-7777','888-888-8888','999-999-9999','000-000-0000');

var TSL:TStringList;
    TPD:TPhoneData;
    i,index:integer;
    phone:string;
begin
randseed;
TSL:=TStringList.Create;
TSL.Sorted:=true;
for i := 0 to 100 do
   begin
   phone:=phoneSeed[random(9)];
   if TSL.Find(phone, index) then
      TPhoneData(TSL.Objects[index]).updateCallData(now-random(100))
   else
      TSL.AddObject(phone,TPhoneData.Create(phone,now,now));
   end;
for i := 0 to 9 do
   begin
   if TSL.Find(phoneSeed[i], index) then
      begin
      TPD:=TPhoneData(TSL.Objects[index]);
      ShowMessage(Format('Phone # %s, first called %s, last called %s, num calls %d', [TPD.PhoneNumber, FormatDateTime('mm-dd-yyyy',TPD.firstCalledDate), FormatDateTime('mm-dd-yyyy',TPD.lastCalledDate), TPD.callCount]));
      end;
   end;
end;
于 2009-05-05T16:40:36.150 回答
0

抱歉,无法编辑我的帖子(当时未注册)。一旦数据库中的所有记录都被迭代,数据将被丢弃。该函数不会经常被调用。它基本上将被用作确定人们在一段时间内从我们已有的记录中订购的频率的一种方式,因此实际上只需要生成一个单独的列表。

数据将在列表创建期间保持不变。也就是说,在读取最后一条数据库记录之前,需要搜索所有电话号码。

于 2009-05-05T15:57:58.013 回答
0

我会建议使用DeCAL(在 sf.net 上)DMap 而不是 TStringList 将项目存储在内存中。您可以指定电话是键并存储包含其余记录的记录/类结构。

所以你的 Record 类将是:


  TPhoneData = class
    number: string;
    access_count: integer;
    added: TDateTime.
     ...
  end;

然后在代码中:


  procedure TSomeClass.RegisterPhone(number, phoneData);
  begin
    //FStore created in Constructor as FStore := DMap.Create;
    FStore.putPair([number, phoneData])
  end;
  ...
  procedure TSoemClass.GetPhoneAndIncrement(number);
  var
    Iter: DIterator;
    lPhoneData: TPhoneData;
  begin
    Iter := FStore.locate([number]);
    if atEnd(Iter) then
      raise Exception.CreateFmt('Number %s not found',[number])
    else
    begin
      lPhoneData := GetObject(Iter) as TPhoneData;
      lPhoneData.access_count = lPhoneData.access_count + 1;
      //no need to save back to FStore as it holds a pointer to lPhoneData
    end;
  end;

DMap 实现了一个红/黑树,因此数据结构可以免费为您排序键。您还可以使用 DHashMap 来获得相同的效果并(可以说)提高速度。

DeCAL 是我最喜欢的数据结构库之一,建议任何进行内存存储操作的人来看看。

希望有帮助

于 2009-05-05T22:12:14.977 回答