Вывод результатов выборки в текстовом виде
Сегодня мы будем решать очередную интересную задачу. У меня возникла необходимость
вывести результаты выборки данных в текстовом виде, наподобие того, как это делает 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;
|
Выполняем тестовый прогон. Я получил такой результат в поле:
Теперь займемся выводом полученных данных. Для того, чтобы данные выводились красиво,
в столбик, необходимо определить максимальную длину значения в каждом поле. Есть два
варианта, как это сделать:
- использовать тип данных поля;
- определить максимальную длину значения поля во всем наборе данных перебором всех
значений.
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 кБ).
|