Большой архив статей, книг, документации по программированию, вебдизайну, компьютерной графике, сетям, операционным системам и многому другому
 
<Добавить в Избранное>    <Сделать стартовой>    <Реклама на сайте>    <Контакты>
  Главная Документация Программы Обои   Экспорт RSS E-Books
 
 

  Раздел: Компьютерная документация -> Программирование -> Delphi / Pascal

 

Фильтрация данных в Delphi

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

Немного теории.

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

Есть два варианта с помощью которых можно задать условие на фильтрацию. Можно использовать их по отдельности или оба сразу.
Первый - строковое значение в свойстве Filter.
Второй - это условие описанное в обработчике события OnFilterRecord.
Начинается фильтрация как только свойство Filtered, установлено в True.

Свойство Filter.

Сначала опишу возможности которые предоставляет свойство Filter, а во второй части главы будет рассказано как можно использовать событие OnFilterRecord. Именно свойство Filter нужно использовать в первую очередь, т.к. это дает значительный выигрыш в производительности при обработке больших по объему наборов данных (по сравнению с событием OnFilterRecord). Особенно это заметно при большом количестве записей в таблице (~свыше 100.000), ну и конечно многое зависит от мощности машины. При малом объеме обрабатываемых данных, разница в скорости не заметна.

Ну что ж приступим. Допустим, есть таблица с полями Фамилия, Имя, Отчество. Нужно фильтровать данные по одному из полей (на выбор), т.е. пользователь должен иметь возможность выбрать по какому из полей фильтровать данные. Для тех кто в танке термины "поле" и "столбец" - обозначают одно и тоже.
Задача стоит такая отсеивать данные по мере ввода искомой строки в Edit.

Кидаем на форму компоненты Combobox и Edit. Combobox будет использоваться для выбора столбца, а в Edit будет вводится искомое слово. Заполняем свойство Items в Combobox именами столбцов. Ставим свойство Style у Combobox равным csDropDownList (чтоб что попало не вводили). Затем в обработчике события Edit1Change пишем следующее: 

procedure TForm1.Edit1Change(Sender: TObject);
begin
if Length(Edit1.Text) > 0 then
begin
ADOTable1.Filtered:=false;
ADOTable1.Filter:=Combobox1.Text + ' LIKE ' + #39 + Edit1.Text + '%' + #39;
ADOTable1.Filtered:=true;
end
else ADOTable1.Filtered:=false;
end;

Жмем F9 - вуаля. Как видите, все довольно просто. Код простой и понятный. На всякий случай перевожу на русский - если Edit не пустой (при пустом вылетит ошибка), отключаем фильтрацию, формируем строку фильтра, запускаем фильтрацию, если Edit пустой - отключаем фильтрацию.

В самом простом случае, если не нужно давать возможность выбора определенного столбца, а фильтровать по одному конкретному полю(например "ФАМИЛИЯ"), строка фильтра выглядела бы следующим образом:

ADOTable1Fil.Filter:='ФАМИЛИЯ LIKE '+ #39 + Edit1.Text + '%' + #39;

Строка-условие фильтра означает следующее - выбрать те записи из столбца 'ФАМИЛИЯ', которые начинаются с тех же символов, что и набраны в Edit1.Text.
Особое внимание обратите на пробелы - "потеряете" пробел - не будет работать.

Ключевое слово LIKE позволяет по заданному шаблону сравнивать строки. При этом нужно знать следующее:
символ '_' (подчеркивание) – заменяет любой одиночный символ,
символ '%' (процент) – заменяет любую последовательность из символов.

Знак #39 - означает номер символа ' (одинарная кавычка) в кодовой таблице ASCII. Дело в том, что значение на фильтрацию нужно указывать в одинарных кавычках, а так как одинарные кавычки используются в Delphi для ограничения строк, то чтобы внутри строки поставить одинарную кавычку, её нужно поставить дважды. Конструкция #39 + Edit1.Text + '%' + #39 идентична '''' + Edit1.Text + '%' + ''''. Неправда ли первый вариант как-то попонятней. Можно также использовать спец. функцию QuotedStr, которая возвращает строку окаймленную одинарными кавычками.
В общем следующие три варианта формирования строки абсолютно одинаково работают и какой вариант проще использовать, решайте сами.

ADOTable1.Filter:='ФАМИЛИЯ LIKE '+ '''' + Edit1.Text + '%' + '''';
ADOTable1.Filter:='ФАМИЛИЯ LIKE '+ QuotedStr(edit1.Text+ '%');
ADOTable1Fil.Filter:='ФАМИЛИЯ LIKE '+ #39 + Edit1.Text + '%' + #39; 

Идем дальше. Если, например, нужно искать любое вхождение искомой строки в записях, а не начиная с первого символа, то строка фильтра выглядела бы следующим образом:
ADOTable1.Filter:='ФАМИЛИЯ LIKE ' + #39 + '%' + Edit1.Text + '%' + #39; 

Заметили '%' перед Edit1.Text? Переводится так - выбрать те записи из столбца 'ФАМИЛИЯ', в которых есть последовательность символов, которые набраны в Edit1.Text.

Все это прекрасно, но часто нужно фильтровать по нескольким столбцам. Тут применяется немного др. конструкция.
Добавим еще два компонента Edit на форму. Combobox использовать не будем, его можно удалить, т.к. у нас каждое поле (Edit) будет отвечать за ввод информации по определенному столбцу. Еще добавим кнопочку, она будет запускать фильтрацию. Казалось бы следующая конструкция должна корректно работать:
procedure TForm1.Button1Click(Sender: TObject);
begin
ADOTable1.Filtered:=false;
ADOTable1.Filter:='ФАМИЛИЯ LIKE '+ #39 + Edit2.Text + '%' + #39 +' AND ' + 'ИМЯ LIKE '+ #39 + Edit3.Text + '%' + #39 + ' AND ' + 'ОТЧЕСТВО LIKE ' + #39 + Edit4.Text + '%' + #39;
ADOTable1.Filtered:=true;
end; 

Она и работает, но только в том случае, если данные введены во все три поля. Если одно из полей ввода(Edit) пустое - вылетает ошибка "Аргументы имеют неверный тип".
Впрочем, есть способ с этим бороться. Сначала код:
procedure TForm1.Button1Click(Sender: TObject);
var filtr, // формируемая строка фильтра
add: string;
begin
ADOTable1.filtered:=false;
filtr:='';
if length(edit2.text) > 0 then
filtr:= 'ФАМИЛИЯ LIKE '+ #39 + Edit2.Text + '%' + #39;

if length(edit3.text) > 0 then
begin
if length(filtr) > 0 then add:= ' and ' else add:='';
filtr:=filtr + add + 'ИМЯ LIKE '+ #39 + Edit3.Text + '%' + #39;
end;

if length(edit4.text) > 0 then
begin
if length(filtr) > 0 then add:= ' and ' else add:='';
filtr:=filtr + add + 'ОТЧЕСТВО LIKE '+ #39 + Edit4.Text + '%' + #39;
end;

if length(filtr) > 0 then
begin
ADOTable1.Filter:= filtr;
ADOTable1.filtered:=true;
end
else Showmessage('Все поля пусты!');

end;

Смысл такой, нужно провести проверку, пустое поле ввода или нет, а затем уже формировать строку фильтра. Причем начиная со второго поля ввода нужно проверять и строку фильтра. Это нужно для последующего правильного формирования строки.
Например если поле Edit2(Фамилия) пустое, то соответственно и строка фильтра будет пуста(filtr:='') , а, например, поле Edit3(ИМЯ) заполнено, это значит строка фильтра должна иметь вид 'ИМЯ LIKE ' + #39 + Edit3.Text + '%' + #39 ,
а не ' and ' + 'ИМЯ LIKE '+ #39 + Edit3.Text + '%' + #39 , (т.е. без ' and ' вначале).
Следующий фрагмент кода это и выполняет: 

.....
if length(edit3.text) > 0 then
begin
if length(filtr) > 0 then add:= ' and ' else add:='';
filtr:=filtr + add + 'ИМЯ LIKE '+ #39 + Edit3.Text + '%' + #39;
end;
.....

Опять перевожу - если Edit3 не пустое, то проверяем строку фильтра(filtr) если она не пуста, то прибавляем к сторке фильтра ' and ' + 'ИМЯ LIKE '+ #39 + Edit3.Text + '%' + #39, если пуста, то просто 'ИМЯ LIKE '+ #39 + Edit3.Text + '%' + #39.

Все. В конце присваиваем сформированную строку(filtr) свойству ADOTable1.Filter и запускаем фильтрацию. 

.....
if length(filtr) > 0 then
begin
ADOTable1.Filter:= filtr;
ADOTable1.filtered:=true;
end
else Showmessage('Все поля пусты!');
.....

Соответственно, если на этом этапе filtr='', значит ни одно из полей не было заполнено, о чем и выводим соответствующую надпись.

Объяснение немного запутанное, но стоит внимательно посмотреть на код и все станет ясно. Забегая вперед, скажу, что если использовать событие OnFilterRecord, то необходимость в проверке наличия символов в поле ввода отпадает(т.е. его удобней и проще использовать), но при этом падает скорость фильтрации. Впрочем это заметно только при обработке больших по объему наборов данных. Напоследок скажу, кроме операции and можно использовать и другие логические операции or, not, xor, при формирования строки-условия фильтрации по нескольким столбцам, хотя такая необходимость возникает достаточно редко.
Здесь можно скачатьрабочий пример фильтрации на основе свойства Filter. 

Событие OnFilterRecord.

Разберемся с событием OnFilterRecod.
Посмотрим на заголовок процедуры 

procedure TForm1.Table1FilterRecord(DataSet: TDataSet; var Accept: Boolean);

DataSet - имя фильтруемого набора данных. Accept имеет тип Boolean, а это значит может принимать два значения true - принять запись, false - отклонить. Кто не дружит с английским перевожу: accept -принимать, брать, соглашаться.
Принцип такой - в обработчике OnFilterRecord последовательно перебираются все записи определенного поля(полей) таблицы данных на предмет соответствия условию фильтрации.
Для наглядности несколько примеров:
Напомню еще раз, что Accept - переменная булевского типа, а это значит можно применять логические операторы(=, <>, <, >, <=, >=, and, or, not) для присвоения ей некоторого значения.
Допустим, в таблице есть поля ГОД и МЕСЯЦ которые имеют целочисленный тип и содержат год и месяц рождения кого либо.

TForm1.Table1FilterRecord(DataSet: TDataSet; var Accept: Boolean);
begin
Accept:= (DataSet['ГОД'] > 1973);
end;
Где то в программе

Table1.Filtered:=true;

В результате в DBGrid отобразятся записи, у которых значение поля ГОД больше 1973.
Если написать в обработчике такую строку: 

Accept:= (DataSet['ГОД'] > 1973) and ((DataSet['МЕСЯЦ'] > 5) and (DataSet['МЕСЯЦ'] < 9));

то отобразятся записи тех людей, которые родились после 1973 год летом.
По-моему все понятно - DataSet['ИМЯ_СТОЛБЦА'] "вытягивает" очередное значение записи указанного столбца, а дальше Accept:= некоторое_логическое_условие;
Ради эксперимента поставьте Accept:=true; и выведутся все записи, если Accept:=false; - ни одной.

Если по какой-то причине Вы хотите обратится к DataSet не по имени столбца, а по его номеру, то можно использовать конструкцию DataSet.Fields[НОМЕР_СТОЛБЦА].AsString вместо DataSet['ИМЯ_СТОЛБЦА'].

С теорией покончено, переходим к практике.

Допустим, в таблице есть поле ФАМИЛИЯ (тип строка) и есть поле ввода искомой строки - Edit1.
Нам нужно, чтобы при вводе очередного символа происходила фильтрация (сначала по первому символу, потом по первому и второму и т.д.). Другими словами, нужно определить некоторое условие в OnFilterRecord, а в обработчике события OnEdit1Change запустить фильтрацию - Table1. Filtered:= true;.
Итак начнем ваять OnFilterRecord. 

procedure TForm1.Table1FilterRecord(DataSet: TDataSet; var Accept: Boolean);
var FieldVal:string;
begin
FieldVal := DataSet['ФАМИЛИЯ'];
Accept := copy(FieldVal, 1, length(edit1.text)) = edit1.Text;
end;

FieldVal - значение очередной записи в заданном столбце, в нашем случае в столбце ФАМИЛИЯ.
Дальше так - функция copy копирует часть строки переменной "FieldVal" начиная с первой позиции "1" на длину искомой строки "length(edit1.text)", потом идет сравнение строки полученной с помощью функции copy и строки введенной в edit1.Text. Если строки совпадают, то запись принимается, если нет - не принимается.
Если фильтрация должна происходить без учета регистра букв, то обработчик должен иметь следующий вид: 

FieldVal := DataSet['ФАМИЛИЯ'];
Accept := copy(AnsiUpperCase(FieldVal), 1, length(edit1.text)) = AnsiUpperCase(edit1.Text);

Осталось только активизировать наш обработчик, как уже говорилось выше в событии OnEdit1Change примерно так: 

procedure TForm1.Edit1Change(Sender: TObject);
begin
table1.Filtered:=false;
table1.Filtered:=true;
end;

Вот и все. Почти. Есть еще один способ "посимвольной" фильтрации, но он работает немного не так как предыдущий пример. Сначала приведу код: 

procedure TForm1.Table1FilterRecord(DataSet: TDataSet; var Accept: Boolean);
begin
accept:=pos(Edit1.Text, DataSet['ФАМИЛИЯ'])<>0);
end;

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

accept:=pos(AnsiUpperCase(Edit1.Text), AnsiUpperCase(DataSet['ФАМИЛИЯ']))<>0;

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

Label1.Caption := IntToStr(table1.RecordCount);

В данном случае ее можно разместить в обработчике события OnEdit1Change сразу после активизации процедуры фильтрации.
Здесь можно скачать рабочий пример фильтрации на основе события OnFilterRecord.

PS. Каждый из способов имеет свои преимущества и недостатки. Свойство Filter дает ощутимый выигрыш в скорости обработки данных, а событие OnFilterRecord намного "гибче" и удобней в использовании. Применяйте свойство Filter в тех случаях если можно обойтись без использования OnFilterRecord.
Если же Вы применяете оба способа, то стоит помнить, что обработчик события дополняет, а не замещает свойство Filter, т.е если включена фильтрация и св-во фильтр содержит значение фильтра, то обработчик события OnFilterRecord и фильтр(Filter) связаны логическим отношением "AND" (выполняются оба). 

Автор: Губарев Михаил
Источник: delphibd.sk6.ru



  Добавить закладку на материал: 


Ссылки по теме
Алгоритмы Сортировки. Часть 1
Введение в Delphi 8
Работа с реестром в Delphi
Delphi и ресурсы компьютера
Советы начинающим программировать на Delphi
Структуры и базы данных, методы сортировки

Вся документация Pascal и Delphi

 

Компьютерная документация от А до Я - Главная

 

 
Интересное в сети
 
10 новых программ
CodeLobster PHP Edition 3.7.2
WinToFlash 0.7.0008
Free Video to Flash Converter 4.7.24
Total Commander v7.55
aTunes 2.0.1
Process Explorer v12.04
Backup42 v3.0
Predator 2.0.1
FastStone Image Viewer 4.1
Process Lasso 3.70.4
FastStone Image Viewer 4.0
Xion Audio Player 1.0.125
Notepad GNU v.2.2.8.7.7
K-Lite Codec Pack 5.3.0 Full


Наши сервисы
Рассылка новостей. Подпишитесь на рассылку сейчас и вы всегда будете в курсе последних событий в мире информационных технологий.
Новостные информеры. Поставьте наши информеры к себе и у вас на сайте появится дополнительный постоянно обновляемый раздел.
Добавление статей. Если вы являетесь автором статьи или обзора на тему ИТ присылайте материал нам, мы с удовольствием опубликуем его у себя на сайте.
Реклама на сайте. Размещая рекламу у нас, вы получите новых посетителей, которые могут стать вашими клиентами.
 
Это интересно
 

Copyright © CompDoc.Ru
При цитировании и перепечатке ссылка на www.compdoc.ru обязательна. Карта сайта.
 
Rambler's Top100