Чтение онлайн

ЖАНРЫ

Мир InterBase. Архитектура, администрирование и разработка приложений баз данных в InterBase/FireBird/Yaffil

Востриков С М

Шрифт:

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

IF (NEW.ID IS NULL) THEN

NEW.ID = GEN_ID(GEN_TABLE_EXAMPLE_ID, 1) ;

Сначала в операторе IF...THEN проверяем идентификатор ID на наличие какого-либо значения, ведь он может быть сгенерирован на клиенте. Если значением NEW.ID является NULL, то вызываем функцию GEN_ID, которая увеличивает значение генератора GEN_TABLE_EXAMPLE_ID на единицу и затем возвращает полученное число, которое присваивается полю NEW.ID Таким образом, мы "налету" изменили значения во вставляемой записи!

Кроме контекстной переменной NEW, существует ее зеркальный аналог - переменная OLD. В отличие от NEW, OLD содержит старые значения записи, которые удаляются или изменяются.

Например, мы можем использовать переменную OLD для получения значений записей, которые удаляются из таблицы:

CREATE TRIGGER Table_example_adO FOR Table_example

ACTIVE AFTER DELETE POSITION 0

AS

BEGIN

IF (OLD.id>1000) THEN

BEGIN

/*..do something..*/

OLD.ID=10;

END

END

Здесь мы создаем триггер, который срабатывает ПОСЛЕ УДАЛЕНИЯ (AFTER DELETE) Как видите, мы можем получить доступ к уже удаленным данным Конечно, присвоение OLD.1D=10; не имеет никакого смысла - присвоенное значение пропадет на выходе из триггера. Однако этот пример показывает, что мы можем перехватить удаляемые значения и записать, например, в некую таблицу, где хранится история всех изменений.

Использование контекстных переменных часто вызывает множество вопросов Дело в том. что в различных видах триггеров NEW и OLD используются по- разному, а в некоторых их вообще невозможно использовать. Если мы рассмотрим триггер в нашем примере, то он вызывается ДО ВСТАВКИ. О каких значениях OLD может идти речь? Ведь вставляется совершенно новая запись! И действительно, контекстная переменная OLD не может быть использована в триггерах BEFORE/AFTER INSERT. А переменная NEW не может быть использована в BEFORE/AFTER DELETE. Обе этих переменные одновременно могут быть использованы в триггерах BEFORE/AFTER UPDATE, причем изменять что-либо можно, только используя переменную NEW (действительно, что можно изменять в удаляющихся значениях, доступных через OLD?), и только в триггерах BEFORE INSERT/UPDATE.

Довольно сложные правила использования, не так ли? Давайте попробуем формализовать их в виде простых правил, которые сведены в таблицу 1.3. В ней мы опишем, как можно применять контекстные переменные в различных триггерах Эту таблицу удобно использовать в качестве подсказки при разработке триггеров.

В этой таблице для каждой контекстной переменной заведено по два столбца - "Читать" и Изменять", отражающих возможные действия с этими переменными В столбце 6 строчек - по числу типов триггеров. Например, если на пересечении типа триггера и возможного действия с контекстной переменной NEW стоит Y, это значит, что в данном типе триггер можно читать или одновременно читать и менять данные Если стоит N/A. то в этом триггере нельзя осуществить это действие с данной контекстной переменной.

Использование контекстных переменных NEW и OLD в триггерах

Тип триггера

Контекстныепеременные

NEW

OLD

Читать

Изменять

Читать

Изменять

BEFORE INSERT

Y

Y

N/A

N/A

AFTER INSERT

Y

N/A

N/A

N/A

BEFORE UPDATE

Y

Y

Y

N/A

AFTER UPDATE

Y

N/A

Y

N/A

BEFORE DELETE

N/A

N/A

Y

N/A

AFTER DELETE

N/A

N/A

Y

N/A

Наиболее широкие

возможности предоставляет использование NEW и OLD в операции обновления. Ведь таким образом мы можем сравнить текущее (OLD) и новое (NEW) значения и предпринять какие-то действия. Например, такой триггер будет очень полезен для вычисления текущих остатков товара на складе при приходе/расходе товара

Управление состоянием триггера

По умолчанию триггер создается активным, т. е. он будет срабатывать при осуществлении соответствующей операции. Состоянием триггера управляет ключевое слово ACTIVE в заголовке Если же триггер сделать неактивным, то он не будет исполняться при возникновении операции. Это бывает полезным при осуществлении каких-либо внеплановых операций надданными, например массовой заливке данных или ручном исправлении данных. Чтобы отключить триггер, необходимо выполнить команду DDL:

ALTER TRIGGER <tngger_name> INACTIVE;

Обратите внимание, что это команда относится к Data Definition'Language, и ее нельзя вызвать из хранимых процедур или других триггеров. Вообще говоря, существует способ управлять состоянием триггеров с помощью модификации системных таблиц. Конечно, модификация системных таблиц является недокументированным способом работы с триггерами и рекомендовать ее мы не будем, но для иллюстрации возможностей работы с системными таблицами InterBase приведем пример. Для того чтобы установить состояние триггера в INACTIVE, достаточно выполнить следующую команду:

UPDATE rdb$triggers trg

SET erg id >$t.rigger_inactive = l

WHERE trg.rdb$trigger_name='TABLE_EXAMPLE_AD0'

Эта команда аналогична по действию вышеприведенной команде DDL, но ее можно вызывать в других триггерах и процедурах.

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

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

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

В общем, это был пример того, что не следует делать в триггерах. Другим примером того, чего не следует делать в триггерах, является изменение данных в той же таблице, к которой привязан триггер, не через контекстные переменные, а с помощью обычных SQL-команд INSERT/UPDATE/DELETE. Например некий триггер на вставку вызывает хранимую процедуру, внутри которой происходит вставка записи в ту же таблицу. Вставка опять вызовет срабатывание нашего триггера, и возникнет зацикливание. Следует очень внимательно относиться к использованию триггеров, так как зацикливание в ряде случаев может привести к аварийному завершению сервера InterBase.

Ошибки и исключения в триггерах

Если база достаточно сложная (лучше сказать, достаточно реальная), то вам никак не избежать появления ошибок. Более того, ошибки типа "конфликт с другими пользователями" являются повседневным и нормальным явлением в многопользовательской среде. Как InterBase обрабатывает ошибки в триггерах? Ведь ситуация может быть достаточно нетривиальная - например, вставка записи в главную таблицу запускает хранимую процедуру, которая вставляет записи в подчиненные таблицы, причем при вставке в подчиненные таблицы срабатывают триггеры на вставку, которые получают новые значения генераторов и подставляют их в нужные поля. Можно представить не один подобный уровень вложенности. Что произойдет, когда где-то в "дальних" ветках этого дерева событий возникнет ошибка?

Поделиться с друзьями: