7. Триггеры
Триггер — это PL/SQL блок, который хранится в базе данных и срабатывает (выполняется) в ответ на указанное событие.
Триггер может быть определён на таблицу, представление, схему (владельца схемы) или базу данных (всех пользователей).
Типы событий триггеров:
- DML-команда (
DELETE
,INSERT
илиUPDATE
). - DDL-команды (
CREATE
,ALTER
илиDROP
). - Операции базы данных, такие как
SERVERERROR
,LOGON
,LOGOFF
,STARTUP
илиSHUTDOWN
.
Пример создания триггера:
CREATE OR REPLACE TRIGGER check_salary_trg
-- перед INSERT в Employees или UPDATE столбцов Salary или Job_ID
BEFORE INSERT OR UPDATE OF Salary, Job_ID ON Employees
[REFERENCING OLD AS old_name | NEW AS new_name] -- можно переименовывать OLD и NEW
FOR EACH ROW -- строковый триггер, для каждой строки (не для всей DML-команды)
BEGIN
-- NEW — объект типа строки Employees, хранит строку после DML-команды
-- OLD — то же самое, но до DML-операции
check_salary(:NEW.Job_ID, :NEW.Salary); -- вызов процедуры check_salary
END;
/
Триггеры делятся на:
- Триггеры базы данных. Срабатывают при возникновении DML, DDL или системного события в схеме или базе данных.
- Триггеры приложений. Срабатывают при возникновении события в конкретном приложении.
Триггеры можно использовать для:
- Безопасности.
- Аудита.
- Целостности данных.
- Ссылочной целостности.
- Репликации (копирования) таблиц.
- Автоматического вычисления производных данных.
- Ведения журнала событий.
Допустимые типы триггеров:
- Простые DML-триггеры (
BEFORE
,AFTER
,INSTEAD OF
(только для view)). - Составные (compound) триггеры.
- Не DML-триггеры (DDL-события, события базы данных).
Триггеры бывают:
- Уровня команды (по умолчанию). Срабатывают один раз на событие триггера, даже тогда, когда не затронута ни одна из строк.
- Строковыми. Нужно прописать
FOR EACH ROW
, срабатывают один раз на каждую затронутую строку по событию триггера, не срабатывают, если событие не затронуло ни одной строки.
Можно так же задавать условия для выполнения триггеров.
В теле триггера можно использовать конструкции (если триггер состоит из нескольких DML-операций):
IF DELETING
,IF UPDATING
,IF INSERTING
,
чтобы определить, какое событие вызвало триггер, который слушает несколько событий.
CREATE OR REPLACE TRIGGER triggerName
[BEFORE | AFTER] INSERT OR UPDATE OR DELETE ON Table_Name
BEGIN
IF DELETING THEN
-- ...
ELSIF UPDATING THEN
-- ...
ELSIF INSERTING THEN
-- ...
END IF;
END;
/
Можно так же использовать WHEN
для строковых триггеров.
CREATE OR REPLACE TRIGGER check_salary_trg
BEFORE INSERT OR UPDATE OF Salary, Job_ID ON Employees
FOR EACH ROW
/*
проверяем, что мы действительно обновляем столбец Job_ID или Salary
или же вставляем новую строку
(без этого триггер сработает на UPDATE, даже если фактическое значение не изменилось).
*/
WHEN (
OLD.Job_ID IS NULL OR OLD.Salary IS NULL
OR OLD.Job_ID != NEW.Job_ID OR OLD.Salary != NEW.Salary
)
-- Заметим, что внутри WHEN пишем «OLD», а внутри BEGIN..END — «:OLD».
BEGIN
check_salary(:NEW.Job_ID, :NEW.Salary);
END;
/
О NEW
и OLD
(доступны только в строковых триггерах):
DML-команда | OLD | NEW |
---|---|---|
INSERT | NULL | введённое значение |
UPDATE | значение до обновления | значение после обновления |
DELETE | удаляемое значение | NULL |
Краткое описание модели выполнения триггера:
- Выполняются все
BEFORE
триггеры уровня команды. - Для каждой строки, затронутой триггером:
- Выполняются все строковые
BEFORE
триггеры - Выполняется DML-команда и проверка ограничений целостности.
- Выполняются все строковые
AFTER
триггеры.
- Выполняются все строковые
- Выполняются все
AFTER
триггеры уровня команды.
У триггера есть 2 состояния: ENABLE
и DISABLE
. Синтаксис:
CREATE OR REPLACE TRIGGER triggerName
BEFORE INSERT ON tableName FOR EACH ROW
DISABLE -- создание выключенного триггера
BEGIN
-- код
END;
/
-- триггер можно выключить/включить, например, так:
ALTER TRIGGER triggerName DISABLE; -- выключаем
ALTER TRIGGER triggerName ENABLE; -- включаем
- Протестируйте каждую операцию с данными, вызывающую срабатывание триггера, а также операции с данными, не вызывающие триггеры.
- В каждом случае протестируйте условие
WHEN
. - Вызовите срабатывание триггера непосредственно из операции с основными данными, а также косвенно из процедуры.
- Проверьте влияния триггера на другие триггеры.
- Проверьте влияния других триггеров на данный триггер.
Информацию о триггерах можно посмотреть в словарях USER_OBJECTS
, USER/ALL/DBA_TRIGGERS
. Синтаксис ошибок триггеров можно посмотреть в словаре USER_ERRORS
.
Составной (compound) триггер — это один триггер на таблицу, позволяющий задать действия для каждой из следующих четырёх точек синхронизации:
- Перед вызывающей командой.
- Перед каждой строкой, на которую влияет вызывающая команда.
- После каждой строки, на которую влияет вызывающая команда.
- После вызывающей команды.
Мутирующая таблица — это:
- Таблица, которая изменяется с помощью команды
UPDATE
,DELETE
илиINSERT
, или - Таблица, которая может быть обновлена под действием ограничения
DELETE CASCADE
.
Составные триггеры можно использовать для:
- Программирования подхода, в котором вы хотите, чтобы действия, которые вы выполняете для разных точек синхронизации, могли совместно использовать общие данные.
- Накопления строк, предназначенных для второй таблицы; так что вы можете периодически их вставлять.
- Того, чтобы избежать ошибки мутирующей таблицы
(ORA-04091)
, разрешив накопление строк, предназначенных для второй таблицы, а затем их массовую вставку.
Ограничения составных триггеров:
- Составной триггер должен быть DML-триггером и определяться на таблицу или представление.
- Тело составного триггера должно быть блоком составного триггера, написанным на языке PL/SQL.
- Тело составного триггера не может иметь блок Инициализации, поэтому оно не может содержать раздел Исключений.
- Исключение, которое происходит в одном разделе, должно быть обработано в том же разделе. Он не может передать управление другому разделу.
:OLD
и:NEW
нельзя использовать в разделах Объявления,BEFORE STATEMENT
иAFTER STATEMENT
.- Только раздел
BEFORE EACH ROW
может изменять значение:NEW
. - Порядок срабатывания составных триггеров не гарантируется, если вы не используете
FOLLOWS
.
Ограничение триггеров на мутирующие таблицы:
- Сессия, использующая вызывающий триггер оператор, не может изменять мутирующую таблицу или выполнять к ней запросы.
- Это ограничение не позволяет триггеру видеть несогласованный набор данных.
- Это ограничение применяется ко всем триггерам, использующим раздел
FOR EACH ROW
. - Представления, изменённые в триггерах
INSTEAD OF
, не считаются мутирующими.
Триггеры LOGON
и LOGOFF
. Пример:
CREATE OR REPLACE TRIGGER logon_trig
AFTER LOGON ON SCHEMA
BEGIN
INSERT INTO log_trig_table(user_id, log_date, action)
VALUES (USER, SYSDATE, 'Logging on');
END;
/
CREATE OR REPLACE TRIGGER logoff_trig
BEFORE LOGOFF ON SCHEMA
BEGIN
INSERT INTO log_trig_table(user_id, log_date, action)
VALUES (USER, SYSDATE, 'Logging off');
END;
/
Можно использовать команду CALL
внутри триггера:
CREATE OR REPLACE PROCEDURE log_execution IS
BEGIN
DBMS_OUTPUT.PUT_LINE('log_exection: Employee Inserted');
END;
/
CREATE OR REPLACE TRIGGER log_employee
BEFORE INSERT ON EMPLOYEES
CALL log_execution -- точка с запятой не требуется
/
Преимущества триггеров на события базы данных:
- Повышают безопасность данных:
- Обеспечивают расширенные и сложные проверки безопасности.
- Обеспечивают расширенный и комплексный аудит.
- Повышают целостность данных:
- Применение ограничений динамической целостности данных.
- Применение сложных ограничений ссылочной целостности.
- Гарантия того, что связанные операции выполняются вместе неявно.
Системные привилегии, необходимые для управления триггерами:
- Привилегия
CREATE/ALTER/DROP (ANY) TRIGGER
, которая позволяет создавать триггеры в любой схеме. - Привилегия
ADMINISTER DATABASE TRIGGER
, которая позволяет создавать триггеры базы данных. - Привилегия
EXECUTE
(если триггер ссылается на объекты, которых нет в схеме).
- Триггеры разрабатываются для:
- Выполнения связанных действий.
- Централизации глобальных операций.
- Не нужно разрабатывать триггеры:
- Если функциональность уже встроена в сервер Oracle.
- Которые дублируют уже существующие триггеры.
- Можно создавать хранимые процедуры и вызывать их в триггере, если PL/SQL код очень длинный.
- Чрезмерное использование триггеров может привести к сложным взаимозависимостям, которые трудно поддерживать в больших приложениях.