Продолжение статьи “Лечение копипаста”

Как было заявлено при декларации возможностей создаваемой программы, поиск должен быть глобальным, по всем сущностям. Но как это организовать на практике, если нужно искать в разных таблицах, а результат формировать в одном запросе? В этом нам поможет SQL-команда UNION.

Задача кажется простой: надо собрать запрос из нескольких SELECT, объединив их командой UNION ALL. Главное условие, чтобы такое объединение работало, – совпадение типов объединяемых полей. В нашем случае такое совпадение имеется.

Вот что можно узнать о команде UNION из курса обучения “Продвинутый уровень. Часть 1”.

Объединение результатов нескольких запросов. UNION

Вы можете объединить результаты отдельных запросов при соблюдении нескольких условий:

  1. Количество колонок в запросах должно быть одинаковым.
  2. Соответствующие колонки должны иметь совместимые типы.
  3. Названия полей результирующего запроса берутся из первого запроса.
  4. Группировки действуют только в пределах каждого отдельного запроса.
  5. Сортировка действует только для всей выборки.
SELECT <параметры выборки>
UNION [ALL]
SELECT <параметры выборки>
UNION [ALL]SELECT <параметры выборки>
[ORDER BY <параметры сортировки>]

Варианты объединения

КомандаДействие
UNIONОбъединить и исключить повторяющиеся строки
UNION ALLОбъединить все строки

Не вдаваясь пока в подробности реализации интерфейса поиска, могу сказать, что потребуется SQL-запрос, который будет присвоен свойству кнопки dbSQL. Кнопка настроена на отображение SQL-запроса в табличном представлении.

Так как тема предыдущей статьи была “Лечение копипаста”, я не буду приводить исходный текст процедуры формирования SQL-запроса, потому что там был сплошной копипаст. Так вот, кроме процедур, для лечения копипаста может понадобиться таблица или несколько таблиц, в которые можно записать те данные, которые нужно обработать в цикле.

Для каждой сущности (таблицы) создадим числовые идентификаторы:

const
  // категории сущностей
  CT_CLASS = 1;
  CT_TYPE = 2;
  CT_METHOD = 3;
  CT_FUNCTION = 4;
  CT_PROPERTY = 5;
  CT_VAR = 6;
  CT_EVENT = 7;
  CT_CONST = 8;
  CT_TASK = 9;

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

Примечание: если вы планируете использовать подобные данные только в циклах обработки, то можно не создавать массивы, а добавить таблицу, в которую однократно записать все данные, а потом извлекать их запросом. Этот способ хорошо подходит, если данные статичны, не требуют корректировок или произвольного доступа. Но если нужен доступ по индексу или состав данных ещё не до конца сформирован, то удобней использовать массивы: для доступа к данным не нужно каждый раз писать запрос к базе; их легче править.
var
  // имена категорий
  CTNames: array[1..9] of string;
  CTTables: array[1..9] of string;
  CTFilters: array[1..9] of string;
  CTNameFld: array[1..9] of string;

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

procedure InitVar;
begin
  // названия сущностей
  CTNames[CT_CLASS]:= 'Класс';
  CTNames[CT_TYPE]:= 'Тип';
  CTNames[CT_METHOD]:= 'Метод';
  CTNames[CT_FUNCTION]:= 'Функция ';
  CTNames[CT_PROPERTY]:= 'Свойство';
  CTNames[CT_VAR]:= 'Переменная ';
  CTNames[CT_EVENT]:= 'Событие';
  CTNames[CT_CONST]:= 'Константа';
  CTNames[CT_TASK]:= 'Задача';
  // таблицы хранения сущностей
  CTTables[CT_CLASS]:= 'classType';
  CTTables[CT_TYPE]:= 'classType';
  CTTables[CT_METHOD]:= 'funcProc';
  CTTables[CT_FUNCTION]:= 'funcProc';
  CTTables[CT_PROPERTY]:= 'property';
  CTTables[CT_VAR]:= 'property';
  CTTables[CT_EVENT]:= 'classEvent';
  CTTables[CT_CONST]:= 'typeConst';
  CTTables[CT_TASK]:= 'task';
  // фильтры сущностей
  CTFilters[CT_CLASS]:= '(isType = 0)';
  CTFilters[CT_TYPE]:= '(isType = 1)';
  CTFilters[CT_METHOD]:= '(id_classType IS NOT NULL)';
  CTFilters[CT_FUNCTION]:= '(id_classType IS NULL) AND (isGroup=0)';
  CTFilters[CT_PROPERTY]:= '(id_classType IS NOT NULL)';
  CTFilters[CT_VAR]:= '(id_classType IS NULL)';
  CTFilters[CT_EVENT]:= '';
  CTFilters[CT_CONST]:= '';
  CTFilters[CT_TASK]:= '(isGroup=0)';
  // поля отображения
  CTNameFld[CT_CLASS]:= 'name';
  CTNameFld[CT_TYPE]:= 'name';
  CTNameFld[CT_METHOD]:= '( (SELECT name FROM classType WHERE classType.id = funcProc.id_classType) || "."|| funcProc.name ) as name';
  CTNameFld[CT_FUNCTION]:= 'name';
  CTNameFld[CT_PROPERTY]:= '( (SELECT name FROM classType WHERE classType.id = property.id_classType) || "."|| property.name ) as name';
  CTNameFld[CT_VAR]:= 'name';
  CTNameFld[CT_EVENT]:= '( (SELECT name FROM classType WHERE classType.id = classEvent.id_classType) || "."|| classEvent.name ) as name';
  CTNameFld[CT_CONST]:= 'name';
  CTNameFld[CT_TASK]:= 'name';
end;

Выглядит всё это довольно громоздко, но зато позволяет реализовать элегантную процедуру формирования запроса.

 
procedure frmSearchResult_btnUpdate_OnClick (Sender: TObject; var Cancel: boolean);
var
  tmpSQL: string;
  s: string;
  i: integer;
 
  // функция формирует запрос для выборки данных по категории
  function SEL( ACategory:integer; ):string;
  begin
    Result :=
      ' SELECT '+chr(13)+//
      '   '+CTNameFld[ACategory]+', '+chr(13)+// название категории
      '   "'+CTNames[ACategory]+'",'+chr(13)+// название категории
      '   description, '+chr(13)+// описание категории
      '   '+IntToStr(ACategory)+', '+chr(13)+// номер категории
      '   id_example, '+chr(13)+// ID примера
      '   id '+chr(13)+// ID записи категории
      ' FROM '+CTTables[ACategory]+chr(13)+ // таблица хранения
      ' WHERE ( (name LIKE "%'+s+'%") OR (description LIKE "%'+s+'%") )'+chr(13);
    if CTFilters[ACategory] <> '' then
      Result := Result + ' AND '+CTFilters[ACategory]+chr(13);
  end;
 
begin
  s := frmSearchResult.edtSearchText.Text;
  // если строка поиска пустая, то ничего не ищем
  if s = '' then
  begin
    Cancel := True;
    Exit;
  end;
  // формируем поиск по всем сущностям
  tmpSQL := '';
  for i:=1 to Length(CTNames) do
  begin
    tmpSQL := tmpSQL + SEL(i);
    if i <> Length(CTNames) then
      tmpSQL := tmpSQL + ' UNION ALL '+chr(13);//
  end;
  frmSearchResult.btnUpdate.dbSQL := tmpSQL;
end;

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

Продолжение “Три главные кнопки”

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *