Счетчик HotLog

На главнуюЧто я делаю...Программы автора и не только...Творчество
Статьи автораКнига отзывов и предложенийОбо мне, любимомФотоальбом

 
 

Вывод результатов выборки в текстовом виде

Сегодня мы будем решать очередную интересную задачу. У меня возникла необходимость вывести результаты выборки данных в текстовом виде, наподобие того, как это делает SQL Query Analyzer (здесь можно посмотреть результаты выборки в текстовом виде).

Итак, приступим.

Я не стал делать ничего сложного, бросил на форму 4 компонента: TADOConnection, TADODataSet, TMemo и TButton.

Сначала означим свойства соединения (MS SQL Server 2000, компьютер называется MINICHDEN, у вас параметры могут отличаться):

Provider   SQLOLEDB.1
Name   Connection
ConnectionString   Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial Catalog=Northwind;Data Source=MINICHDEN

Теперь свойства набора данных:

Connection   Connection
CommandText   select CustomerID, CompanyName, ContactName, ContactTitle, Address from Customers
Name   DataSet

Многострочное поле:

Name   Memo
Anchors   [akLeft,akTop,akRight,akBottom]
ScrollBars   ssBoth
Font.Name   Courier New
Font.Size   10
Lines  

И, наконец, кнопка:

Name   Button
Anchors   [akRight,akBottom]
Caption   Старт
TabOrder   0

Всё, форма готова, теперь займемся программированием.

Дважды щелкните по кнопке и в модуле формы у вас появится обработчик события OnClick. Напишем его:

procedure TForm1.ButtonClick(Sender: TObject);
begin
  // Открываем набор данных.
  DataSet.Open;
  try
    // Выводим количество полученных записей.
    Memo.Lines.Add('Получено ' + IntToStr(DataSet.RecordCount) + ' записей.');
  finally
    // Закрываем набор данных.
    DataSet.Close;
  end;
end;

Выполняем тестовый прогон. Я получил такой результат в поле:

Получено 91 записей.

Теперь займемся выводом полученных данных. Для того, чтобы данные выводились красиво, в столбик, необходимо определить максимальную длину значения в каждом поле. Есть два варианта, как это сделать:

  • использовать тип данных поля;
  • определить максимальную длину значения поля во всем наборе данных перебором всех значений.

SQL Query Analyzer использует первый способ, так как он более быстрый. Мы же пойдем по второму пути, невзирая на ощутимую потерю в скорости работы (в нашем случае это некритично, так как выборка небольшая), так как второй способ позволяет вывести данные более компактно, то есть красивее. Возможно, для больших выборок придется пользоваться первым способом.

Для этого заведем массив длин, размерность которого совпадает с количеством полей в наборе данных и заполним его максимальными значениями длин полей набора данных, не забыв про заголовки полей. Вот что у нас получается:

procedure TForm1.ButtonClick(Sender: TObject);
const
  // Строковое представление значения Null.
  NULL_VALUE = 'NULL';
var
  // Массив максимальных длин значений полей.
  MaxFieldValueLengths: array of Integer;
  // Индекс поля.
  FieldIndex: Integer;
  // Поле выборки.
  Field: TField;
  // Значение поля.
  FieldValue: String;
  // Длина значения поля.
  FieldValueLength: Integer;
  // Счетчик.
  I: Integer;
begin
  // Открываем набор данных.
  DataSet.Open;
  try
    // Устанавливаем длину массива.
    SetLength(MaxFieldValueLengths, DataSet.FieldCount);

    // Заполняем массив длинами заголовков полей.
    for FieldIndex := 0 to DataSet.FieldCount - 1 do
      MaxFieldValueLengths[FieldIndex] := Length(DataSet.Fields.Fields[FieldIndex].FieldName);

    // Находим максимальные длины данных.
    while not DataSet.Eof do
    begin
      // Бежим по набору данных и определяем длину значения каждого поля.
      for FieldIndex := 0 to DataSet.FieldCount - 1 do
      begin
        Field := DataSet.Fields.Fields[FieldIndex];
        if Field.IsNull then
          FieldValue := NULL_VALUE
        else
          FieldValue := Field.AsString;
        FieldValueLength := Length(FieldValue);
        if FieldValueLength > MaxFieldValueLengths[FieldIndex] then
          MaxFieldValueLengths[FieldIndex] := FieldValueLength;
      end;
      DataSet.Next;
    end;

    // Проверим результаты работы.
    for I := 0 to Length(MaxFieldValueLengths) - 1 do
      Memo.Lines.Add(IntToStr(MaxFieldValueLengths[I]));
  finally
    // Закрываем набор данных.
    DataSet.Close;
  end;
end;

Снова выполняем тестовый прогон. В поле получаем такой результат:

10
36
23
30
46
15
13
10
11
17
17

Хорошо, едем дальше. Осталось самое сложное - вывод самих результатов. Нам понадобится длина строки со значениями всех полей. Рассчитаем ее как сумму максимальных длин для каждого поля и добавим еще на промежутки между полями.

procedure TForm1.ButtonClick(Sender: TObject);
const
  // Строковое представление значения Null.
  NULL_VALUE = 'NULL';
  // Строковое представление пробела.
  SPACE_DELIMITER: String = ' ';
var
  // Массив максимальных длин значений полей.
  MaxFieldValueLengths: array of Integer;
  // Индекс поля.
  FieldIndex: Integer;
  // Поле выборки.
  Field: TField;
  // Значение поля.
  FieldValue: String;
  // Длина значения поля.
  FieldValueLength: Integer;
  // Длина строки записи набора данных.
  DataSetLineLength: Integer;
  // Строка записи набора данных.
  DataSetLine: String;
  // Смещение значения поля в общей строке относительно начала.
  Shift: Integer;
  // Указатель на строку записи набора данных.
  PDataSetLine: PChar;
begin
  // Открываем набор данных.
  DataSet.Open;
  try
    // Устанавливаем длину массива.
    SetLength(MaxFieldValueLengths, DataSet.FieldCount);

    // Заполняем массив длинами заголовков полей.
    for FieldIndex := 0 to DataSet.FieldCount - 1 do
      MaxFieldValueLengths[FieldIndex] := Length(DataSet.Fields.Fields[FieldIndex].FieldName);

    // Находим максимальные длины данных.
    while not DataSet.Eof do
    begin
      // Бежим по набору данных и определяем длину значения каждого поля.
      for FieldIndex := 0 to DataSet.FieldCount - 1 do
      begin
        Field := DataSet.Fields.Fields[FieldIndex];
        if Field.IsNull then
          FieldValue := NULL_VALUE
        else
          FieldValue := Field.AsString;
        FieldValueLength := Length(FieldValue);
        if FieldValueLength > MaxFieldValueLengths[FieldIndex] then
          MaxFieldValueLengths[FieldIndex] := FieldValueLength;
      end;
      DataSet.Next;
    end;

    // Выводим данные.
    // Находим длину строки набора данных.
    DataSetLineLength := 0;
    for FieldIndex := 0 to Dataset.FieldCount - 1 do
      Inc(DataSetLineLength, MaxFieldValueLengths[FieldIndex]);
    // Добавляем по одному символу на промежутки между полями.
    Inc(DataSetLineLength, Dataset.FieldCount - 1);
    // Выделяем под строку достаточное для хранения данных записи место.
    SetLength(DataSetLine, DataSetLineLength);

    // Имена полей.
    // Заполняем строку пробелами.
    FillChar(DataSetLine[1], Length(DataSetLine) * SizeOf(DataSetLine[1]), SPACE_DELIMITER[1]);
    // Первоначально сдвига с начала строки нет.
    Shift := 0;
    for FieldIndex := 0 to Dataset.FieldCount - 1 do
    begin
      // Получаем значение поля.
      FieldValue := DataSet.Fields.Fields[FieldIndex].FieldName;
      // Получаем указатель на нужную нам позицию.
      PDataSetLine := PChar(Integer(DataSetLine) + Shift * SizeOf(DataSetLine[1]));
      // Вставляем значение поля в выходную строку.
      CopyMemory(PDataSetLine, Pointer(FieldValue), Length(FieldValue) * SizeOf(FieldValue[1]));
      // Увеличиваем сдвиг на максимальную длину поля.
      Shift := Shift + MaxFieldValueLengths[FieldIndex] + 1;
    end;

    // Выводим результат.
    Memo.Lines.Add(DataSetLine);
  finally
    // Закрываем набор данных.
    DataSet.Close;
  end;
end;

Снова выполняем тестовый прогон. В поле получаем такой результат:

CustomerID CompanyName                          ContactName             ContactTitle                   Address                                       

Думаю, что только что написанный участок кода нуждается в дополнительных подробных комментариях:

  • Для чего я произвожу установку длины строки? Это делается для увеличения скорости. Если заранее выделить место под строку, то при ее изменении не производится перераспределения памяти, которое сильно тормозит работу. Ускорение достигается при условии, что длины строки хватает для хранения результата.
  • Использование CopyMemory вместо привычной конкатенации строк или форматирования дает прирост в скорости выполнения.
  • Работать с указателями выгоднее, чем со строками с точки зрения производительности, но гораздо сложнее. Будьте внимательны, чтобы избежать множества ошибок.

Рассмотрим подробно эту строчку

    FillChar(DataSetLine[1], Length(DataSetLine) * SizeOf(DataSetLine[1]), SPACE_DELIMITER[1]);

Здесь в функцию передается указатель на начало строки, то есть первый символ. Длина строки умножается на объем занимаемой одним символом памяти. Индекс символа строки не играет абсолютно никакой роли, так как используется лишь для указания типа данных (здесь - Char). Значение вычисляется еще на этапе компиляции. То есть абсолютно корректна будет и такая строчка, даже если длина строки меньше:

    FillChar(DataSetLine[1], Length(DataSetLine) * SizeOf(DataSetLine[MaxInt]), SPACE_DELIMITER[1]);

Посмотрим теперь на последний параметр, передаваемый в процедуру. Вас, вероятно, удивляет, почему константа SPACE_DELIMITER объявлена как строка, но передается в функцию первый (и единственный) ее символ. К этому вопросу мы обязательно вернемся чуть позже.

const
  // Строковое представление пробела.
  SPACE_DELIMITER: String = ' ';
...
    FillChar(DataSetLine[1], Length(DataSetLine) * SizeOf(DataSetLine[1]), SPACE_DELIMITER[1]);

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

procedure TForm1.ButtonClick(Sender: TObject);
const
  // Строковое представление значения Null.
  NULL_VALUE = 'NULL';
  // Строковое представление пробела.
  SPACE_DELIMITER: String = ' ';
var
  // Массив максимальных длин значений полей.
  MaxFieldValueLengths: array of Integer;
  // Индекс поля.
  FieldIndex: Integer;
  // Поле выборки.
  Field: TField;
  // Значение поля.
  FieldValue: String;
  // Длина значения поля.
  FieldValueLength: Integer;
  // Длина строки записи набора данных.
  DataSetLineLength: Integer;
  // Строка записи набора данных.
  DataSetLine: String;
  // Смещение значения поля в общей строке относительно начала.
  Shift: Integer;
  // Указатель на строку записи набора данных.
  PDataSetLine: PChar;
begin
  Memo.Lines.Clear;
  // Открываем набор данных.
  DataSet.Open;
  try
    // Устанавливаем длину массива.
    SetLength(MaxFieldValueLengths, DataSet.FieldCount);

    // Заполняем массив длинами заголовков полей.
    for FieldIndex := 0 to DataSet.FieldCount - 1 do
      MaxFieldValueLengths[FieldIndex] := Length(DataSet.Fields.Fields[FieldIndex].FieldName);

    // Находим максимальные длины данных.
    while not DataSet.Eof do
    begin
      // Бежим по набору данных и определяем длину значения каждого поля.
      for FieldIndex := 0 to DataSet.FieldCount - 1 do
      begin
        Field := DataSet.Fields.Fields[FieldIndex];
        if Field.IsNull then
          FieldValue := NULL_VALUE
        else
          FieldValue := Field.AsString;
        FieldValueLength := Length(FieldValue);
        if FieldValueLength > MaxFieldValueLengths[FieldIndex] then
          MaxFieldValueLengths[FieldIndex] := FieldValueLength;
      end;
      DataSet.Next;
    end;

    // Выводим данные.
    // Находим длину строки набора данных.
    DataSetLineLength := 0;
    for FieldIndex := 0 to Dataset.FieldCount - 1 do
      Inc(DataSetLineLength, MaxFieldValueLengths[FieldIndex]);
    // Добавляем по одному символу на промежутки между полями.
    Inc(DataSetLineLength, Dataset.FieldCount - 1);
    // Выделяем под строку достаточное для хранения данных записи место.
    SetLength(DataSetLine, DataSetLineLength);

    // Имена полей.
    // Заполняем строку пробелами.
    FillChar(DataSetLine[1], Length(DataSetLine) * SizeOf(DataSetLine[1]), SPACE_DELIMITER[1]);
    // Первоначально сдвига с начала строки нет.
    Shift := 0;
    for FieldIndex := 0 to Dataset.FieldCount - 1 do
    begin
      // Получаем значение поля.
      FieldValue := DataSet.Fields.Fields[FieldIndex].FieldName;
      // Получаем указатель на нужную нам позицию.
      PDataSetLine := PChar(Integer(DataSetLine) + Shift * SizeOf(DataSetLine[1]));
      // Вставляем значение поля в выходную строку.
      CopyMemory(PDataSetLine, Pointer(FieldValue), Length(FieldValue) * SizeOf(FieldValue[1]));
      // Увеличиваем сдвиг на максимальную длину поля.
      Shift := Shift + MaxFieldValueLengths[FieldIndex] + 1;
    end;
    // Выводим результат.
    Memo.Lines.Add(DataSetLine);

    // Разделитель заголовка и набора данных.
    // Заполняем строку символами "минус".
    FillChar(DataSetLine[1], Length(DataSetLine) * SizeOf(DataSetLine[1]), '-');
    Shift := 0;
    for FieldIndex := 0 to Dataset.FieldCount - 2 do
    begin
      // Здесь сдвиг делаем на максимальную длину значения поля.
      PDataSetLine := PChar(Integer(DataSetLine) + (Shift + MaxFieldValueLengths[FieldIndex]) * SizeOf(DataSetLine[1]));
      CopyMemory(PDataSetLine, Pointer(SPACE_DELIMITER), Length(SPACE_DELIMITER) * SizeOf(SPACE_DELIMITER[1]));
      Shift := Shift + MaxFieldValueLengths[FieldIndex] + 1;
    end;
    Memo.Lines.Add(DataSetLine);

    // Непосредственно вывод данных.
    DataSet.First;
    // В цикле строим строки для всех записей.
    while not DataSet.Eof do
    begin
      // Заполняем строку пробелами.
      FillChar(DataSetLine[1], Length(DataSetLine) * SizeOf(DataSetLine[1]), SPACE_DELIMITER[1]);
      Shift := 0;
      for FieldIndex := 0 to Dataset.FieldCount - 1 do
      begin
        Field := DataSet.Fields.Fields[FieldIndex];
        if Field.IsNull then
          FieldValue := NULL_VALUE
        else
          FieldValue := Field.AsString;
        PDataSetLine := PChar(Integer(DataSetLine) + Shift * SizeOf(DataSetLine[1]));
        CopyMemory(PDataSetLine, Pointer(FieldValue), Length(FieldValue) * SizeOf(FieldValue[1]));
        Shift := Shift + MaxFieldValueLengths[FieldIndex] + 1;
      end;
      Memo.Lines.Add(DataSetLine);
      DataSet.Next;
    end;
  finally
    // Закрываем набор данных.
    DataSet.Close;
  end;
end;

Выполняем тестовый прогон и видим, что результат именно тот, что нам нужен:

CustomerID CompanyName                          ContactName             ContactTitle                   Address                                       
---------- ------------------------------------ ----------------------- ------------------------------ ----------------------------------------------
ALFKI      Alfreds Futterkiste                  Maria Anders            Sales Representative           Obere Str. 57                                 
ANATR      Ana Trujillo Emparedados y helados   Ana Trujillo            Owner                          Avda. de la Constitucion 2222                 
ANTON      Antonio Moreno Taqueria              Antonio Moreno          Owner                          Mataderos  2312                               
AROUT      Around the Horn                      Thomas Hardy            Sales Representative           120 Hanover Sq.                               
BERGS      Berglunds snabbkop                   Christina Berglund      Order Administrator            Berguvsvagen  8                               
BLAUS      Blauer See Delikatessen              Hanna Moos              Sales Representative           Forsterstr. 57                                
BLONP      Blondesddsl pere et fils             Frederique Citeaux      Marketing Manager              24, place Kleber                              
BOLID      Bolido Comidas preparadas            Martin Sommer           Owner                          C/ Araquil, 67                                
BONAP      Bon app'                             Laurence Lebihan        Owner                          12, rue des Bouchers                          
BOTTM      Bottom-Dollar Markets                Elizabeth Lincoln       Accounting Manager             23 Tsawassen Blvd.                            
BSBEV      B's Beverages                        Victoria Ashworth       Sales Representative           Fauntleroy Circus                             
CACTU      Cactus Comidas para llevar           Patricio Simpson        Sales Agent                    Cerrito 333                                   
CENTC      Centro comercial Moctezuma           Francisco Chang         Marketing Manager              Sierras de Granada 9993                       
CHOPS      Chop-suey Chinese                    Yang Wang               Owner                          Hauptstr. 29                                  
COMMI      Comercio Mineiro                     Pedro Afonso            Sales Associate                Av. dos Lusiadas, 23                          
CONSH      Consolidated Holdings                Elizabeth Brown         Sales Representative           Berkeley Gardens 12  Brewery                  
DRACD      Drachenblut Delikatessen             Sven Ottlieb            Order Administrator            Walserweg 21                                  
DUMON      Du monde entier                      Janine Labrune          Owner                          67, rue des Cinquante Otages                  
EASTC      Eastern Connection                   Ann Devon               Sales Agent                    35 King George                                
ERNSH      Ernst Handel                         Roland Mendel           Sales Manager                  Kirchgasse 6                                  
FAMIA      Familia Arquibaldo                   Aria Cruz               Marketing Assistant            Rua Oros, 92                                  
FISSA      FISSA Fabrica Inter. Salchichas S.A. Diego Roel              Accounting Manager             C/ Moralzarzal, 86                            
FOLIG      Folies gourmandes                    Martine Rance           Assistant Sales Agent          184, chaussee de Tournai                      
FOLKO      Folk och fa HB                       Maria Larsson           Owner                          Akergatan 24                                  
FRANK      Frankenversand                       Peter Franken           Marketing Manager              Berliner Platz 43                             
FRANR      France restauration                  Carine Schmitt          Marketing Manager              54, rue Royale                                
FRANS      Franchi S.p.A.                       Paolo Accorti           Sales Representative           Via Monte Bianco 34                           
FURIB      Furia Bacalhau e Frutos do Mar       Lino Rodriguez          Sales Manager                  Jardim das rosas n. 32                        
GALED      Galeria del gastronomo               Eduardo Saavedra        Marketing Manager              Rambla de Cataluna, 23                        
GODOS      Godos Cocina Tipica                  Jose Pedro Freyre       Sales Manager                  C/ Romero, 33                                 
GOURL      Gourmet Lanchonetes                  Andre Fonseca           Sales Associate                Av. Brasil, 442                               
GREAL      Great Lakes Food Market              Howard Snyder           Marketing Manager              2732 Baker Blvd.                              
GROSR      GROSELLA-Restaurante                 Manuel Pereira          Owner                          5? Ave. Los Palos Grandes                     
HANAR      Hanari Carnes                        Mario Pontes            Accounting Manager             Rua do Paco, 67                               
HILAA      HILARION-Abastos                     Carlos Hernandez        Sales Representative           Carrera 22 con Ave. Carlos Soublette #8-35    
HUNGC      Hungry Coyote Import Store           Yoshi Latimer           Sales Representative           City Center Plaza 516 Main St.                
HUNGO      Hungry Owl All-Night Grocers         Patricia McKenna        Sales Associate                8 Johnstown Road                              
ISLAT      Island Trading                       Helen Bennett           Marketing Manager              Garden House Crowther Way                     
KOENE      Koniglich Essen                      Philip Cramer           Sales Associate                Maubelstr. 90                                 
LACOR      La corne d'abondance                 Daniel Tonini           Sales Representative           67, avenue de l'Europe                        
LAMAI      La maison d'Asie                     Annette Roulet          Sales Manager                  1 rue Alsace-Lorraine                         
LAUGB      Laughing Bacchus Wine Cellars        Yoshi Tannamuri         Marketing Assistant            1900 Oak St.                                  
LAZYK      Lazy K Kountry Store                 John Steel              Marketing Manager              12 Orchestra Terrace                          
LEHMS      Lehmanns Marktstand                  Renate Messner          Sales Representative           Magazinweg 7                                  
LETSS      Let's Stop N Shop                    Jaime Yorres            Owner                          87 Polk St. Suite 5                           
LILAS      LILA-Supermercado                    Carlos Gonzalez         Accounting Manager             Carrera 52 con Ave. Bolivar #65-98 Llano Largo
LINOD      LINO-Delicateses                     Felipe Izquierdo        Owner                          Ave. 5 de Mayo Porlamar                       
LONEP      Lonesome Pine Restaurant             Fran Wilson             Sales Manager                  89 Chiaroscuro Rd.                            
MAGAA      Magazzini Alimentari Riuniti         Giovanni Rovelli        Marketing Manager              Via Ludovico il Moro 22                       
MAISD      Maison Dewey                         Catherine Dewey         Sales Agent                    Rue Joseph-Bens 532                           
MEREP      Mere Paillarde                       Jean Fresniere          Marketing Assistant            43 rue St. Laurent                            
MORGK      Morgenstern Gesundkost               Alexander Feuer         Marketing Assistant            Heerstr. 22                                   
NORTS      North/South                          Simon Crowther          Sales Associate                South House 300 Queensbridge                  
OCEAN      Oceano Atlantico Ltda.               Yvonne Moncada          Sales Agent                    Ing. Gustavo Moncada 8585 Piso 20-A           
OLDWO      Old World Delicatessen               Rene Phillips           Sales Representative           2743 Bering St.                               
OTTIK      Ottilies Kaseladen                   Henriette Pfalzheim     Owner                          Mehrheimerstr. 369                            
PARIS      Paris specialites                    Marie Bertrand          Owner                          265, boulevard Charonne                       
PERIC      Pericles Comidas clasicas            Guillermo Fernandez     Sales Representative           Calle Dr. Jorge Cash 321                      
PICCO      Piccolo und mehr                     Georg Pipps             Sales Manager                  Geislweg 14                                   
PRINI      Princesa Isabel Vinhos               Isabel de Castro        Sales Representative           Estrada da saude n. 58                        
QUEDE      Que Delicia                          Bernardo Batista        Accounting Manager             Rua da Panificadora, 12                       
QUEEN      Queen Cozinha                        Lucia Carvalho          Marketing Assistant            Alameda dos Canarios, 891                     
QUICK      QUICK-Stop                           Horst Kloss             Accounting Manager             Taucherstra?e 10                              
RANCH      Rancho grande                        Sergio Gutierrez        Sales Representative           Av. del Libertador 900                        
RATTC      Rattlesnake Canyon Grocery           Paula Wilson            Assistant Sales Representative 2817 Milton Dr.                               
REGGC      Reggiani Caseifici                   Maurizio Moroni         Sales Associate                Strada Provinciale 124                        
RICAR      Ricardo Adocicados                   Janete Limeira          Assistant Sales Agent          Av. Copacabana, 267                           
RICSU      Richter Supermarkt                   Michael Holz            Sales Manager                  Grenzacherweg 237                             
ROMEY      Romero y tomillo                     Alejandra Camino        Accounting Manager             Gran Via, 1                                   
SANTG      Sante Gourmet                        Jonas Bergulfsen        Owner                          Erling Skakkes gate 78                        
SAVEA      Save-a-lot Markets                   Jose Pavarotti          Sales Representative           187 Suffolk Ln.                               
SEVES      Seven Seas Imports                   Hari Kumar              Sales Manager                  90 Wadhurst Rd.                               
SIMOB      Simons bistro                        Jytte Petersen          Owner                          Vinb?ltet 34                                  
SPECD      Specialites du monde                 Dominique Perrier       Marketing Manager              25, rue Lauriston                             
SPLIR      Split Rail Beer & Ale                Art Braunschweiger      Sales Manager                  P.O. Box 555                                  
SUPRD      Supremes delices                     Pascale Cartrain        Accounting Manager             Boulevard Tirou, 255                          
THEBI      The Big Cheese                       Liz Nixon               Marketing Manager              89 Jefferson Way Suite 2                      
THECR      The Cracker Box                      Liu Wong                Marketing Assistant            55 Grizzly Peak Rd.                           
TOMSP      Toms Spezialitaten                   Karin Josephs           Marketing Manager              Luisenstr. 48                                 
TORTU      Tortuga Restaurante                  Miguel Angel Paolino    Owner                          Avda. Azteca 123                              
TRADH      Tradicao Hipermercados               Anabela Domingues       Sales Representative           Av. Ines de Castro, 414                       
TRAIH      Trail's Head Gourmet Provisioners    Helvetius Nagy          Sales Associate                722 DaVinci Blvd.                             
VAFFE      Vaffeljernet                         Palle Ibsen             Sales Manager                  Smagsloget 45                                 
VICTE      Victuailles en stock                 Mary Saveley            Sales Agent                    2, rue du Commerce                            
VINET      Vins et alcools Chevalier            Paul Henriot            Accounting Manager             59 rue de l'Abbaye                            
WANDK      Die Wandernde Kuh                    Rita Muller             Sales Representative           Adenauerallee 900                             
WARTH      Wartian Herkku                       Pirkko Koskitalo        Accounting Manager             Torikatu 38                                   
WELLI      Wellington Importadora               Paula Parente           Sales Manager                  Rua do Mercado, 12                            
WHITC      White Clover Markets                 Karl Jablonski          Owner                          305 - 14th Ave. S. Suite 3B                   
WILMK      Wilman Kala                          Matti Karttunen         Owner/Marketing Assistant      Keskuskatu 45                                 
WOLZA      Wolski  Zajazd                       Zbyszek Piestrzeniewicz Owner                          ul. Filtrowa 68                               

Данные отличаются от эталонных по той причине, что они в формате Unicode, а мы ведем работу со String вместо WideString. Использование Unicode не изменило бы отображение данных, так как стандартные визуальные компоненты Delphi его не поддерживают. В случае надобности код нетрудно доработать.

Что ж, задача решена, но меня сильно смущает размер полученного метода. Самое время заняться рефакторингом и оптимизацией.

Я выделил три функции. Это позволило избавиться от дублирования и позволило немного сократить код. Код, правда, потом снова увеличился из-за того, что я переделал его на единоразовый вывод результата выборки вместо построчного. Это дало большой выигрыш в скорости. Итак, вспомогательные функции:

// Вернуть строковое представление значения поля.
function GetFieldValue(
  // Поле.
  const AField: TField): String; forward;

// Заполнить строку символами.
procedure FillStringByChar(
  // Строка.
  var AString: String;
  // Символ.
  const AChar: Char); forward;

// Вставить строку в заданное место.
procedure InjectStringToPosition(
  // Строка-приемник.
  var Destination: String;
  // Вставляемая строка.
  const Source: String;
  // Позиция.
  const Position: Integer); forward;

function GetFieldValue(const AField: TField): String;
const
  // Строковое представление значения Null.
  NULL_VALUE = 'NULL';
begin
  if AField.IsNull then
    Result := NULL_VALUE
  else
    Result := AField.AsString;
end;

procedure FillStringByChar(var AString: String; const AChar: Char);
var
  ALength: Integer;
begin
  ALength := Length(AString);
  if ALength > 0 then
    FillChar(AString[1], ALength * SizeOf(AString[1]), AChar);
end;

procedure InjectStringToPosition(var Destination: String; const Source: String;
  const Position: Integer);
var
  PDestination: PChar;
begin
  PDestination := PChar(Integer(Destination) + Position * SizeOf(Destination[1]));
  CopyMemory(PDestination, Pointer(Source), Length(Source) * SizeOf(Source[1]));
end;

В результате код метода получился таким:

procedure TForm1.ButtonClick(Sender: TObject);
const
  SPACE_DELIMITER: String = ' ';
var
  MaxFieldValueLengths: array of Integer;
  DataSetFieldCount, FieldIndex, FieldValueLength, DataSetLineLength, Shift: Integer;
  Field: TField;
  FieldValue, DataSetLine: String;
  DataSetText: TStringList;
begin
  Memo.Lines.Clear;
  DataSetText := nil;
  DataSet.Open;
  try
    DataSetText := TStringList.Create;
    DataSetFieldCount := DataSet.FieldCount;
    SetLength(MaxFieldValueLengths, DataSetFieldCount);

    for FieldIndex := 0 to DataSetFieldCount - 1 do
      MaxFieldValueLengths[FieldIndex] := Length(DataSet.Fields.Fields[FieldIndex].FieldName);

    while not DataSet.Eof do
    begin
      for FieldIndex := 0 to DataSetFieldCount - 1 do
      begin
        Field := DataSet.Fields.Fields[FieldIndex];
        FieldValue := GetFieldValue(Field);
        FieldValueLength := Length(FieldValue);
        if FieldValueLength > MaxFieldValueLengths[FieldIndex] then
          MaxFieldValueLengths[FieldIndex] := FieldValueLength;
      end;
      DataSet.Next;
    end;

    DataSetLineLength := 0;
    for FieldIndex := 0 to DataSetFieldCount - 1 do
      Inc(DataSetLineLength, MaxFieldValueLengths[FieldIndex]);
    Inc(DataSetLineLength, DataSetFieldCount - 1);
    SetLength(DataSetLine, DataSetLineLength);

    FillStringByChar(DataSetLine, SPACE_DELIMITER[1]);
    Shift := 0;
    for FieldIndex := 0 to DataSetFieldCount - 1 do
    begin
      FieldValue := DataSet.Fields.Fields[FieldIndex].FieldName;
      InjectStringToPosition(DataSetLine, FieldValue, Shift);
      Shift := Shift + MaxFieldValueLengths[FieldIndex] + 1;
    end;
    DataSetText.Add(DataSetLine);

    FillStringByChar(DataSetLine, '-');
    Shift := 0;
    for FieldIndex := 0 to DataSetFieldCount - 2 do
    begin
      InjectStringToPosition(DataSetLine, SPACE_DELIMITER, Shift + MaxFieldValueLengths[FieldIndex]);
      Shift := Shift + MaxFieldValueLengths[FieldIndex] + 1;
    end;
    DataSetText.Add(DataSetLine);

    DataSet.First;
    while not DataSet.Eof do
    begin
      FillStringByChar(DataSetLine, SPACE_DELIMITER[1]);
      Shift := 0;
      for FieldIndex := 0 to DataSetFieldCount - 1 do
      begin
        Field := DataSet.Fields.Fields[FieldIndex];
        FieldValue := GetFieldValue(Field);
        InjectStringToPosition(DataSetLine, FieldValue, Shift);
        Shift := Shift + MaxFieldValueLengths[FieldIndex] + 1;
      end;
      DataSetText.Add(DataSetLine);
      DataSet.Next;
    end;
    Memo.Lines.Assign(DataSetText);
  finally
    DataSetText.Free;
    DataSet.Close;
  end;
end;

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

А теперь как и обещал, объясню, в чем причина странного использования строки для хранения односимвольной константы (SPACE_DELIMITER). Вся причина кроется в методе CopyMemory. Символ ' ' (пробел/space) интерпретируется как символ, а не как строка (как, собственно, и все остальные). Если оставить типа Char, то при обращении к CopyMemory получим ошибку Access Violation.

Вы можете скачать исходники демонстрационного проекта (Delphi 7) (5 кБ).

 
 

08.08.2007

 
     
Hosted by uCoz