Для приложения может быть полезным знать, какое будет значение первичного ключа новой строки без необходимости ожидать завершения транзакции. Например, это общее требование при создании "главной" записи и связи с ней "подчиненных" записей обычно с помощью внешнего ключа в одной транзакции. Довольно неуклюже - а иногда и рискованно - нарушать атомарность задачи создания главная- подчиненная, подтверждая создание главной для получения необходимого вам значения внешнего ключа для подчиненных записей, полагаясь только на триггер.
Приложения, написанные для Firebird, имеют преимущества, благодаря одной особой характеристике генераторов: они не зависят от пользовательских транзакций. Однажды сгенерированное значение не может быть выдано ни одной другой транзакции и не может быть отменено.
Быстрый запрос в его собственной транзакции[122] возвращает значение
SELECT GEN_ID (GEN_PK_CUSTOMER, 1) AS RESULT
FROM RDB$DATABASE;
Если в вашем триггере опущена проверка на пустое значение и просто выполняется:
. . .
AS
NEW.CUSTOMER_ID = GEN_ID (GEN_PK_CUSTOMER, 1);
END ^
то значение, полученное приложением, будет перекрыто вторым "дерганьем" генератора, что нарушит связь с подчиненными записями.
Эта ситуация не является аргументом в пользу генерации ключей только в триггерах. Наоборот, триггер с проверкой на NULL обеспечивает реализацию бизнес-правил при любых условиях.
! ! !
ВНИМАНИЕ! В разработках, где нет хорошей интеграции работы приложений различных разработчиков, или где пользователи имеют свободный доступ к базе данных при использовании инструментов запросов, может оказаться необходимым включение в ваши триггеры более высокого уровня защиты целостности ключей, такого как контроль принадлежности диапазону или другая подходящая форма проверки.
. ! .
Переменная NEW может быть использована для преобразования значения в нечто другое. Общий трюк заключается в использовании триггера (или пары триггеров в версии 1.0.А-) для поддержания "заменителя" столбца для выполнения нечувствительных к регистру поисков по другому столбцу, который может содержать смешанные значения регистра. Триггер читает значение NEW столбца со смешанным регистром, конвертирует его в верхний регистр и записывает в значение NEW столбца поиска. Такой столбец-"заменитель" должен иметь ограничение NOT NULL для гарантии того, что в нем всегда будет значение для поиска:
CREATE TABLE MEMBER (
MEMBER_ID INTEGER NOT NULL PRIMARY KEY,
LAST_NAME VARCHAR (40) NOT NULL,
FIRST_NAME VARCHAR (35),
PROXY_LAST_NAME VARCHAR (40),
MEMBER_TYPE CHAR(3) NOT NULL,
MEMBERSHIP_NUM VARCHAR(13) ,
. . . . );
COMMIT;
/* */
SET TERM ^;
CREATE TRIGGER BA_MEMBER1 FOR MEMBER
ACTIVE BEFORE INSERT OR UPDATE
POSITION 0
AS
BEGIN
. . .
NEW. PROXY_LAST_NAME = UPPER(NEW.LAST_HAME) ;
. . .
END ^
Возможны любые виды преобразований. Предположим, мы хотим получить количество элементов (MEMBERSHIP_NUM), собранное из MEMBER_TYPE, за которым следует строка из десяти цифр, заполненная слева нулями и полученная из сгенерированного первичного ключа таблицы MEMBER. С помощью автоматической генерации в триггере BEFORE INSERT мы можем это осуществить[123]:
CREATE TRIGGER BI_MEMBER2 FOR MEMBER
ACTIVE BEFORE
INSERT POSITION 2
AS
DECLARE VARIABLE ID AS STRING VARCHAR (10);
BEGIN
ID_AS_STRING = CAST (NEW. ID AS VARCHAR (10)) ;
WHILE (NOT (ID_AS_STRING LIKE ' %'))
/* 10-символьная маска */
DO
ID_AS_STRING = '0' || ID_AS_STRING;
NEW.MEMBERSHIP_NUM = NEW.MEMBER_TYPE || ID_AS_STRING;
END ^
Триггеры могут улучшить стандартные ограничения SQL, когда они используются для проверки входных данных и применения значений по умолчанию.
SQL предоставляет ограничения CHECK для гарантии того, что будут сохраняться только "хорошие" данные. Например, значения столбцов, создаваемых на основе следующего домена, ограничены символами в верхнем регистре и цифрами:
CREATE DOMAIN TYPECODE CHAR(3)
CHECK(VALUE IS NULL OR VALUE = UPPER(VALUE));
Это замечательно - и мы хотим усилить это правило. Само по себе такое ограничение вызовет исключение, если любое клиентское приложение попытается передать символы в нижнем регистре. С помощью триггера мы можем полностью убрать исключение, исправляя попытки нарушений на месте:
CREATE TRIGGER BA_ATABLE FOR ATABLE
ACTIVE BEFORE INSERT OR UPDATE
AS
BEGIN
NEW.ATYPECODE = UPPER(NEW.ATYPECODE);
END ^
! ! !
ПРИМЕЧАНИЕ. В настоящий момент Firebird поддерживает триггеры только для таблиц и просмотров. Невозможно создать триггер для домена, однако это было бы элегантным улучшением.
. ! .
В определениях доменов и столбцов вы можете указать значение по умолчанию: DEFAULT. Кажется хорошей идеей устанавливать значение столбца, не допускающего значение NULL В некоторое значение по умолчанию, однако SQL-атрибут DEFAULT на деле оказывается беззубым зверем. Он работает только при двух условиях:
* при использовании операции INSERT;
* если этот столбец не включен во входной список оператора.