Поиск записи с помощью TQuery
Компонент TQuery не предусматривает основанный на индексе поиск, подобный реализованному в компоненте TTable (FindKey, GotoKey и GotoNearest). Поэтому возникает следующий вопрос: как в данных, возвращаемых запросом TQuery, найти определенную запись?
Один из путей поиска в результатах запроса является последовательный поиск. Данный тип поиска стартует в первой строке набора данных и, с помощью цикла, последовательно сравнивает значения полей с искомой величиной. Возможно достижение одного из двух результатов: величина будет найдена (успех) или будет достигнут конец набора данных (неудача). Самый большой недостаток этого способа поиска заключается в том, что он самый медленный, поскольку искомая величина может оказаться в одной их последних записей, а для этого придется перебрать весь набор данных. При неудаче он должен перебрать весь набор данных. При интенсивном поиске данный метод займет большую часть времени вычислений.
Вот функция, выполняющая последовательный поиск в результатах запроса TQuery:
var pb: TProgressBar; begin ... function SeqSearch(AQuery: TQuery; AField, AValue: String): Boolean; begin with AQuery do begin First; while (not Eof) and (not (FieldByName(AField).AsString = AValue)) do Next; SeqSearch := not Eof; end; end;
Данная функция требует три параметра:
AQuery: тип TQuery; компонент TQuery, в котором необходимо выполнить поиск.
AField: тип String; имя поля, с величиной которого проиходит сравнение значение поиска.
AValue: тип String; искомая величина. Если поля имеет тип отличный от типа String, искомая величина должна быть преобразована к типу данных.
Возвращаемая логическая величина указывает на успешность выполнения функции (True) или отсутствие результата поиска (False).
Альтернативой служит использование метода заключения в скобки. На концептуальном уровне данный метод действует отчасти подобно индексу bb-дерева. Он основывается на методе сравнения значения текущей строки набора данных и искомой величины с последующей проверкой на выполнение одного из трех возможных условий:
Величина поля будет больше чем значение поиска, или...
Величина поля будет меньше чем значение поиска, или...
Величина поля равняется значению поиска.
Данный метод сужает область данных, отбрасывая при каждой итерации записи, не удовлетворяющие приведенным выше условиям до тех пор, пока первые два условия выполняться не будут. Полученные данные сравнивается с искомой величиной и, если они совпадают, считается что функция выполнена успешно (success), или окончилась неудачей (failure, если искомая величина ни разу не встретилась, т.е. результат поиска не содержит ни одной строки).
Функционально данный процесс находит поля, удовлетворяющие условиям поиска, за количество итераций меньшее или равное числу записей. При этом возможно только два результата сравнения текущего поля и искомой величины: меньше чем/равняется/больше чем. Первоначально устанавливается диапазон чисел. Меньшая граница диапазона задается целым числом, начало процесса поиска устанавливается на 0 или величину меньшую, чем значение первой строки набора данных. Верхняя граница диапазона является также целым числом, содержащим значение свойства RecordCount экземпляра TQuery. Текущий указатель строки перемещается в в точку, лежащую посередине между нижней и верхней границей диапазона. Значение записи в этой точке сравнивается с искомой величиной. Если значение поля меньше или равно искомой величине, значит искомая величина находится в нижней части набора данных, поэтому верхняя граница диапазона перемещается к позиции текущей строки. Если значение поля больше величины поиска, то искомая величина находится в верхней части набора данных, поэтому к текущему указателю перемещается нижняя граница диапазона. Повторяя этот процесс, количество удовлетворяющих условиям поиска записей при каждой итерации уменьшается в два раза. В конечном счете должна остаться только одна строка.
Код модульной, транспортабельной функции должно выглядеть примерно так:
function Locate(AQuery: TQuery; AField, AValue: string): Boolean; var Hi, Lo: Integer; begin with AQuery do begin First; {Устанавливаем верхнюю границу диапазона строк} Hi := RecordCount; {Устанавливаем нижнюю границу диапазона строк} Lo := 0; {Текущий указатель перемещаем в в точку, лежащую посередине между нижней и верхней границей диапазона} MoveBy(RecordCount div 2); while (Hi - Lo) > 1 do begin {Значение поля больше искомой величины, величина в первой половине} if (FieldByName(AField).AsString > AValue) then begin {Вычисляем нижнюю границу верхнего поддиапазона общего диапазона} Hi := Hi - ((Hi - Lo) div 2); MoveBy(((Hi - Lo) div 2) * -1); end {Найденное поле меньше искомой величины, нужно искать в верхней половине} else begin {Перемещаем вверх нижнюю границу общего диапазона} Lo := Lo + ((Hi - Lo) div 2); MoveBy((Hi - Lo) div 2); end; end; {Обрабатываем нечетную нумерацию строк} if (FieldByName(AField).AsString > AValue) then Prior; Locate := (FieldByName(AField).AsString = AValue) end; end;
Последние строчки были добавлены для обработки ситуации, когда верхняя и нижняя границы диапазона различаются по четности строк.
Данная функция также требует три параметра, как и функция SeqSearch, описанная выше.
Величина, возвращаемая функцией, имеет тип Boolean и указывает на ее удачное или, наоборот, неудачное завершение. Так как процесс поиска перемещает указатель строки, то вызывающее приложение должно принимать во внимание эффект от такого перемещения и при неудачном поиске он должен быть возвращен на место. Например, указатель TBookmark может использоваться для того, чтобы возвращать указатель строки на то место, где он был до неудачного завершения функции.
Чем этот метод лучше последовательного поиска? Во-первых, данный метод не производит сравнение всех строк, как это делает метод последовательного поиска, а опрашивает часть записей. Если искомая величина не располагается в числе первых 1,000 строк, то этот метод окажется быстрее чем метод последовательного поиска. Поскольку этот процесс всегда использует одинаковое количество записей, то время поиска будет одинаковым и когда искомая величина находится в записи с номером 1,000, и когда она находится в записи с номером 90,000. Это в корне отличается от последовательного поиска, когда время поиска напрямую зависит от местонахождения искомой величины.
Эти методы могут использоваться в любых результатах запроса TQuery? Нет. Все дело в технологии: описанные методы пользуются такими понятиями, как направление поиска, нижняя и верхняя границы диапазона. Это означает, что набор данных должен быть последователен и непрерывен, т.е. для получения результатов TQuery должен использовать SQL-запросы, содержащие ключевую фразу ORDER BY. Размер полученного набора данных также является показателем для выбора метода. Метод заключения в скобки выгоднее использовать при большом наборе данных. В случае, когда число записей невелико (1,000 и менее строк), метод последовательного поиска все же будет быстрее.
Как мне найти запись (осуществить 'Find') в TQuery?
Я привел ниже код, который я использую в своей работе, правда, в нем еще необходимо организовать обработку исключительных ситуаций, но это дело времени. Когда пользователь нажимает кнопку "Найти", обработчик события OnClick вызывает процедуру SearchName.
Объявляем: FindSearch : Boolean и инициализируем значением True.
function LookForString(target, source: string): boolean; { в случае игнорирования перед вызовом pos необходимо преобразовать source и target в верхний регистр } begin LookForString := pos(target, source); end; procedure SearchName(searchtype: string; stringtofind: string); var OldCursor: TCursor; CurrentPos: TBookmark; found: boolean; begin if Form1.Query1.State = dsEdit then Form1.Query1.Post; if StringToFind = '' then exit; OldCursor := Screen.Cursor; Screen.Cursor := crHourGlass; with Form1 do begin CurrentPos := Query1.GetBookmark; Query1.DisableControls; found := false; if searchtype <> 'prev' then { первый или следующий } begin if searchtype = 'first' then Query1.First else if not Query1.EOF then Query1.Next; while (not Query1.EOF) and (not found) do begin if LookForString(StringToFind, MemberName) <> 0 then found := true; if not found then Query1.Next; end; end else begin { prev } if not Query1.BOF then Query1.Prior; while (not Query1.BOF) and (not found) do begin if LookForString(StringToFind, MemberName) <> 0 then found := true; if not found then Query1.Prior; end; end; Screen.Cursor := OldCursor; if found then begin FindSearch := false; ChangeFindCaption; UpdateStatusLabel; end else begin MessageDlg('Больше ничего не найдено.', mtInformation, [mbOK], 0); Query1.GotoBookmark(CurrentPos); end; Query1.EnableControls; Query1.FreeBookmark(CurrentPos); end; { конец работы с Form1 } end; procedure TForm1.FindButtonClick(Sender: TObject); begin if FindSearch then SearchName('first', Page0Edit.Text) else SearchName('next', Page0Edit.Text); end;
Взято с https://delphiworld.narod.ru