Поскольку многие современные интерфейсы приложений автоматически создают оператор INSERT, используя выходные столбцы оператора SELECT В качестве входного
списка, то все столбцы помещаются в этот список. Если само приложение не предоставляет значение по умолчанию, то обычным результатом является передача значения NULL. Когда сервер получает значение NULL для такого столбца, он сохраняет в базе данных NULL. Другие ограничения столбца могут вклиниться сюда и вызвать исключение - особенно ограничение NOT NULL - однако значение по умолчанию для столбца никогда не перекроет и не скорректирует любое значение, полученное от клиентского интерфейса.
Вторая проблема связана, конечно, с тем, что значения по умолчанию никогда не применяются, если используется операция изменения.
Короче говоря, триггеры выполняют гораздо более эффективную работу по поддержанию значений по умолчанию, чем это делают атрибуты значения по умолчанию для столбца. Возьмем для примера столбец, основанный на следующем домене:
CREATE DOMAIN MONEY NUMERIC (18, 0)
NOT NULL DEFAULT 0.00;
Триггер BEFORE INSERT OR UPDATE для любого столбца, использующего домен MONEY, реализует значение по умолчанию:
CREATE TRIGGER BI_ACCOUNT FOR ACCOUNT
ACTIVE BEFORE INSERT OR UPDATE
AS
BEGIN
IF (NEW.BALANCE IS NULL) THEN
NEW.BALANCE = 0.00;
END ^
! ! !
СОВЕТ. Вы можете обеспечить поддержание всех значений по умолчанию для таблицы в едином триггерном модуле (версия 1.5 и выше) или в двух параллельных модулях: один для BEFORE INSERT, а другой для BEFORE UPDATE (версия 1.0.x).
. ! .
Триггеры полезны для "автоматического заполнения" контекстной информацией столбцов, созданных для подобных целей. Firebird предоставляет множество контекстных переменных, которые вы можете использовать в операциях такого рода. Вы можете также использовать ваши собственные "флаги", которые вы вычисляете или просто поставляете в виде констант в процессе выполнения триггера.
В следующем примере мы используем триггер AFTER для множества событий с целью автоматического заполнения имени пользователя, даты, времени и идентификатора транзакции для помещения в файл протокола вместе с некоторой информацией о событии. Поскольку регистрируется процесс, то, скорее всего, мы захотим выполнить это в самом конце; присвоим триггеру высокий последовательный номер:
CREATE TRIGGER AA_MEMBER FOR MEMBER
ACTIVE AFTER INSERT OR UPDATE OR DELETE
POSITION 99
AS
DECLARE VARIABLE MEM_ID INTEGER;
DECLARE VARIABLE DML_EVENT CHAR(4);
BEGIN
IF (DELETING) THEN
BEGIN
MEM_ID = OLD.MEMBER_ID;
DML_EVENT = 'DEL ';
END
ELSE
BEGIN
MEM_ID = NEW.MEMBER_ID;
IF (UPDATING) THEN
DML_EVENT = 'EDIT';
ELSE
DML_EVENT = 'NEW ';
END
INSERT INTO PROCESS_LOG (
TRANS_ID,
USER_ID,
MEMBER_ID,
DML_EVENT,
TIME_STAMP)
VALUES (
CURRENT_TRANSACTION,
CURRENT_USER,
:MEM_ID,
:DML_EVENT,
CURRENT_TIMESTAMP) ;
END ^
Конечно, вы также можете заполнять ваши новые или редактируемые строки непосредственно в триггере BEFORE.
Изменение других таблиц
В предыдущем примере мы увидели, как триггеры AFTER могут выполнять изменения в других таблицах для автоматизации управления такими задачами, как ведение протокола. Возможности расширить область действия события DML за пределы непосредственного контекста таблицы и строки, "владеющей" данными, имеют некоторые приложения для управления сложными отношениями.
Поддержание обязательного отношения
Обязательное отношение существует, когда две таблицы связаны через зависимость внешнего ключа и должна существовать по меньшей мере одна строка для каждой первичной строки. Поскольку SQL не предоставляет ограничения "обязательности", нужна логика триггера для осуществления правила "минимум один потомок" не только во время создания, но и при удалении зависимых строк.
Следующий пример вкратце описывает один способ использования триггеров для осуществления такого обязательного отношения главная-подчиненная. В нем предполагается, что первичный ключ главной таблице известен в приложении до того, как посылается новая запись.
Во-первых, создадим две таблицы:
CREATE TABLE MASTER (
ID INTEGER NOT NULL PRIMARY KEY,
DATA VARCHAR(10));
COMMIT;
CREATE TABLE DETAIL (
ID INTEGER NOT NULL PRIMARY KEY,
MASTER_ID INTEGER,
/* Столбец внешнего ключа сознательно сделан в виде, допускающем пустое значение */
DATA VARCHAR(10),
TEMP_FK INTEGER,
CONSTRAINT FK_MASTER FOREIGN KEY(MASTER_ID)
REFERENCES MASTER
ON DELETE CASCADE);
COMMIT;
Когда приложение посылает (post) строки главной и подчиненной таблиц, оно вначале будет передавать подчиненные строки со значением NULL в столбце внешнего ключа и со значением первичного ключа главной таблицы в столбце TEMP FK.
Затем нам нужно исключение, которое будет возникать при попытке нарушения правила обязательности и при удалении последней подчиненной строки. Мы также создадим генератор для подчиненной строки.
CREATE EXCEPTION CANNOT_DEL_DETAIL
'This is the only detail record: it can not be deleted.';
/* Это единственная подчиненная запись: она не может быть удалена */
CREATE GENERATOR GEN_DETAIL;
COMMIT;