Создание генераторов и триггеров в InterBase

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

Общая информация
Вообще, считается хорошим стилем создание первичного ключа в виде уникального числа. Мало того, это наиболее удобный способ для связи таблиц. Такой первичный ключ часто называют "айдишкой" (от слова "ID", идентификатор) таблицы.

Значения такого поля могут задаваться и вручную, но тогда придется вручную же контролировать их уникальность, что практически невозможно при многопользовательском режиме использования БД. Для этого в InterBase был включен механизм "генераторов", аналогичный механизму "последовательностей" в Oracle. Единственная цель генератора - дать уникальное (в его контексте) числовое значение при вызове.

Сами по себе генераторы практически не имеют смысла, так как они никак не связаны с таблицами БД. Для запроса значения генератора применяются триггеры, автоматически вызываемые СУБД при возникновении определенных событий. В основном, триггер, использующий генератор, вызывается по событию "BeforeInsert", то есть перед непосредственной вставкой данных.

Текст создания классического генератора и триггера для него, в общем случае, выглядит следующим образом:

CREATE GENERATOR GEN_%TBL_NAME%;
CREATE TRIGGER NEW_%TBL_NAME% FOR %TBL_NAME%
ACTIVE BEFORE INSERT POSITION 0
AS
begin
  if (NEW.%ID_FIELD% is NULL) then NEW.%ID_FIELD% = gen_id(GEN_%TBL_NAME%,1);
end
Здесь %TBL_NAME% - имя таблицы, для которой создается триггер и генератор, а %ID_FIELD% - имя поля, являющегося первичным ключом.

Реализация
Итак, для реализации утилиты нам необходимо для начала найти список таблиц базы данных, а также их первичных ключей. Для этого мы сделаем запросы к словарю данных базы (словарь данных еще называется метаданными), который содержится в системных таблицах, имена которых начинаются с последовательности "rdb$".

Список таблиц можно получить следующим образом:
select rdb$relation_name from rdb$relations
where (rdb$system_flag=0) and (rdb$view_source is null)
order by rdb$relation_name asc;
Список первичных ключей получаем так:
select i.rdb$field_name from rdb$relation_constraints r, rdb$index_segments i
where r.rdb$relation_name=:TBL_NAME and r.rdb$constraint_type='PRIMARY KEY'
and r.rdb$index_name=i.rdb$index_name order by i.rdb$field_position
Теперь все необходимое для дальнейшей работы получено и мы можем создавать генераторы с триггерами.

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

В поле списка опций (большое белое поле) у нас будут имена таблиц, а надписи на кнопках говорят сами за себя. Как вы видите, ничего сложного. Код обработки нажатия на кнопку "Create generators..." таков:
procedure TForm2.btnCreateClick(Sender: TObject);
var i: integer;
begin
  with dm do for i := 0 to pred(lbTables.Count) do
  begin
    if lbTables.Checked[i] then
      if not HasTrigger(lbTables.Items[i]) then
      begin
        if not tm.InTransaction then tm.StartTransaction;
        try CreateTriggeredGenerator(lbTables.Items[i]);
        except
          tm.Rollback;
          showmessage('Unable to create objects for the table '+lbTables.Items[i]+'.');
        end;
        if tm.InTransaction then
        begin
          tm.Commit;
          showmessage('Objects for the table '+lbTables.Items[i]+' is successfully created.');
        end;
      end;
  end;
end;
Здесь для каждой выбранной в списке таблицы проверяется, существует ли уже триггер с генератором для этой таблицы. Если нет, то они создаются.

Проверка существования триггера выглядит вот таким образом:
function TForm2.HasTrigger(const TableName: string): boolean;
var s: string;
    i: integer;
begin
  Result := false;
  if (Gens.Count < 1) then exit;
  with dm do
  begin
    qHasTrigger.ParamByName('TBL_NAME').Value := TableName;
    qHasTrigger.Open;
    while not qHasTrigger.Eof do
    begin
      s := qHasTrigger.FieldByName('rdb$trigger_source').AsString;
      if (length(s) > 0) then
        for i := 0 to pred(Gens.Count) do
          if pos(UpperCase(Gens[i]),UpperCase(s)) > 0 then Result := true;
      qHasTrigger.Next;
    end;
    qHasTrigger.Close;
  end;
end;


Вот и всё, Удачи!


    No results found.
Отменить.