Лучший метод частого хранения, поиска и изменения большого набора данных в Delphi.

Как лучше всего в delphi создавать и хранить данные, которые часто будут искать и изменять?

По сути, я хотел бы написать функцию, которая ищет в существующей базе данных телефонные номера и отслеживает, сколько раз использовался каждый телефонный номер, первую и последнюю использованную дату. База данных, в которой выполняется поиск, представляет собой журнал размещенных заказов, содержащий номер телефона, который использовался для размещения заказа. Это не база данных SQL или что-то, что можно легко запросить для таких вещей (это старая база данных btrieve), поэтому мне нужно создать способ получения этой информации (чтобы в конечном итоге вывести ее в текстовый файл).

Я думаю создать запись, содержащую номер телефона, две даты и количество использованных раз, а затем добавить запись в динамический массив для каждого телефонного номера. Затем я искал бы массив, запись за записью, для каждой записи в базе данных, чтобы увидеть, есть ли номер телефона для текущей записи в массиве. Затем обновление или создание записи по мере необходимости.

Кажется, что это сработает, но, поскольку в базе данных десятки тысяч записей, это может быть не лучший способ, а довольно медленный и неэффективный. Есть ли лучший способ, учитывая ограниченные действия, которые я могу выполнять с базой данных?

Кто-то предложил вместо массива использовать таблицу MySQL для отслеживания чисел, а затем запрашивать каждое число для каждой записи базы данных. Хотя это кажется еще более накладным!

Большое спасибо за ваше время.


person Community    schedule 05.05.2009    source источник
comment
Нужно ли искать данные много раз и поддерживать их в течение длительного периода времени? Или вы читаете кусок, ищете его, а затем выбрасываете, чтобы никогда больше не использовать?   -  person Scott W    schedule 05.05.2009


Ответы (5)


Я бы зарегистрировал агрегаты в полностью отключенном TClientDataset(cds) и обновлял значения по мере их получения из цикла. Если бы Btrieve можно было сортировать по номеру телефона, было бы намного лучше. Затем используйте данные на компакт-дисках для создания отчета.

(Если вы пойдете этим путем, я предлагаю получить Midas SpeedFix из блог Андреаса Хаусладена, а также другие лучшие материалы, которые вы можете там найти).

person Fabricio Araujo    schedule 05.05.2009

Хорошо, вот старый метод двойного прохода, который хорошо работает и должен хорошо масштабироваться (однажды я использовал этот подход для базы данных с несколькими миллионами записей, это заняло время, но дало точные результаты).

  1. Загрузите и установите Turbo Power SysTools — механизм сортировки очень хорошо подходит для этого процесса.
  2. создайте сортировку с фиксированной записью номера телефона, которую вы будете использовать для сортировки.
  3. Пролистайте свои записи, при каждом заказе добавляйте номер телефона в сортировку.
  4. После завершения первой итерации начните извлекать номера телефонов из сортировки, увеличивайте счетчик, если номер телефона совпадает с последним прочитанным, в противном случае сообщите номер и очистите счетчик.

Этот процесс также можно выполнить с любой базой данных SQL, но мой опыт показывает, что метод сортировки быстрее, чем управление временной таблицей, и дает такие же результаты.

EDIT. Вы сказали, что это база данных BTrieve, почему бы просто не создать ключ по номеру телефона, отсортировать по этому ключу, а затем применить шаг 4 к этой таблице (далее вместо pop). В любом случае вам нужно будет коснуться каждой записи в вашей базе данных, чтобы получить подсчет, индекс/сортировка просто упрощает процесс принятия решений.

Например, предположим, что у вас есть две таблицы, одна таблица клиентов, где будут храниться результаты, а другая таблица заказов. Сортировать по одному и тому же номеру телефона. Затем установите курсор в верхней части обоих списков, а затем примените следующий псевдокод:

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;

Преимущество этого кода в том, что он проходит оба списка один раз. Нет необходимости в поиске, поскольку оба списка отсортированы одинаково, их можно просматривать сверху вниз, выполняя действия только при необходимости. Единственное требование состоит в том, чтобы оба списка имели что-то общее (в данном примере номер телефона) и оба списка можно было сортировать.

Я не занимался случаем, когда есть заказ, а клиента нет. Мое предположение заключалось в том, что заказы не существуют без клиентов и будут пропущены при подсчете.

person skamradt    schedule 05.05.2009

Извините, не могу отредактировать свой пост (не был зарегистрирован). Данные будут удалены после повторения всех записей в базе данных. Функция не будет вызываться часто. По сути, он будет использоваться как способ определения того, как часто люди заказывали в течение определенного периода времени, исходя из уже имеющихся у нас записей, так что на самом деле нам просто нужно составить разовый список.

Данные будут постоянными на время создания списка. То есть все телефонные номера должны присутствовать для поиска до тех пор, пока не будет прочитана самая последняя запись базы данных.

person Community    schedule 05.05.2009

Если вы собираетесь хранить его в памяти и не хотите ничего особенного, вам лучше использовать TStringList, чтобы вы могли использовать функцию Find. Поиск использует выбор Хора или быстрый выбор, локатор 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;
person Marshall Fryman    schedule 05.05.2009

Вместо TStringList я бы рекомендовал использовать DeCAL (на sf.net) DMap для хранения элементов в объем памяти. Вы можете указать, что телефон является ключом, и сохранить структуру Record/Class, содержащую остальную часть записи.

Итак, ваш класс 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 — одна из моих любимых библиотек структур данных, и я бы порекомендовал ее всем, кто занимается операциями хранения в оперативной памяти.

надеюсь, это поможет

person Nazar    schedule 05.05.2009