Время на прочтение
3 мин
Количество просмотров 36K
Рис.1 – художники мутанты ниндзя черепашки
Мутирование таблиц (ошибка ORA-04091) возникает, если в триггере уровня строки выполняется изменение или чтение данных из той же самой таблицы, для которой данный триггер должен был сработать.
Рассмотрим два способа обхода данной ситуации. Первый – через пакет – древний, как удар маваши гери с разворота, смотрится эффектно, но долго готовится и сложен в исполнении. Второй – более свежий и простой – с использованием составных триггеров.
create table turtles
as
select 'Сплинтер' name, 'Крыса' essence from dual union all
select 'Леонардо', 'Художник' from dual union all
select 'Рафаэль', 'Художник' from dual union all
select 'Микеланджело', 'Художник' from dual union all
select 'Донателло', 'Художник' from dual;
NAME | ESSENCE |
---|---|
Сплинтер | Крыса |
Леонардо | Художник |
Рафаэль | Художник |
Микеланджело | Художник |
Донателло | Художник |
Условимся, что когда Сплитер из крысы мутируют в сэнсэя, художники должны будут автоматически превратиться в ниндзя. Казалось бы, для этого должен подойти такой триггер
create or replace trigger tr_turtles_bue
before update of essence
on turtles
for each row
when (
new.name = 'Сплинтер' and old.essence = 'Крыса' and new.essence = 'Сэнсэй'
)
begin
update turtles
set essence = 'Ниндзя'
where essence = 'Художник';
end;
Но при попытке обновить запись
update turtles
set essence = 'Сэнсэй'
where name = 'Сплинтер'
возникает
ORA-04091: table SCOTT.TURTLES is mutating, trigger/function may not see it
Удалим этот триггер
drop trigger tr_turtles_bue;
Способ обхода 1. С помощью пакета и триггера уровня инструкции.
create or replace package pkg_around_mutation
is
bUpdPainters boolean;
procedure update_painters;
end pkg_around_mutation;
/
create or replace package body pkg_around_mutation
is
procedure update_painters
is
begin
if bUpdPainters then
bUpdPainters := false;
update turtles
set essence = 'Ниндзя'
where essence = 'Художник';
end if;
end;
end pkg_around_mutation;
/
create or replace trigger tr_turtles_bue
before update of essence
on turtles
for each row
when (
new.name = 'Сплинтер' and old.essence = 'Крыса' and new.essence = 'Сэнсэй'
)
begin
pkg_around_mutation.bUpdPainters := true;
end tr_turtles_bue;
/
create or replace trigger tr_turtles_bu
after update
on turtles
begin
pkg_around_mutation.update_painters;
end tr_turtles_bu;
/
Способ обхода 2. С помощью составного триггера (compound DML triggers).
Доступно, начиная с Oracle 11g.
create or replace trigger tr_turtles_ue
for update of essence
on turtles
compound trigger
bUpdPainters boolean;
before each row is
begin
if :new.name = 'Сплинтер' and :old.essence = 'Крыса' and :new.essence = 'Сэнсэй' then
bUpdPainters := true;
end if;
end before each row;
after statement is
begin
if bUpdPainters then
update Turtles
set essence = 'Ниндзя'
where essence = 'Художник';
end if;
end after statement;
end tr_turtles_ue;
Пробуем
update turtles
set essence = 'Сэнсэй'
where name = 'Сплинтер'
NAME | ESSENCE |
---|---|
Сплинтер | Сэнсэй |
Леонардо | Ниндзя |
Рафаэль | Ниндзя |
Микеланджело | Ниндзя |
Донателло | Ниндзя |
Даже если вы столкнулись с более сложным случаем мутации, можно использовать приведенную идею обхода. Она, во-первых, связана с тем, что в триггере уровня инструкции, в отличие от триггера уровня строки, мутации не возникает. Во-вторых, можно использовать либо переменные (признаки, защелки, таблицы PL SQL) в специально созданном вспомогательном пакете, либо переменные, глобальные для всех секций составного триггера, что более предпочтительно, начиная с версии Oracle 11g.
Так что теперь и вы знаете кунг-фу. До новых встреч.
Дополнительные материалы: Compound DML Triggers, Мутирование таблиц
TL;DR: Не надо использовать триггер там, где в этом нет никакой необходимости.
Удалите триггер, перенесите логику из триггера в то место, где вызывается DML выражение, которое приводит к срабатыванию триггера. Создайте там процедуру или функцию, если эта логика используется не единожды в приложении.
Перед тем как создать триггер, надо убедится в его необходимости ознакомившись с рекомендациями по применению триггеров, например в офф. документации.
Само появление ошибки — ORA-04091: table is mutating
, говорит, что допущена логическая ошибка в дизайне кода и БД вынуждена прибегнуть к защите от возможной потери целостности данных в мульти-пользовательской среде.
Выше изложеное не раз обсуждалось на различных ресурсах, например, цитирую Тома Кайта на спроси Тома:
My personal opinion — when I hit a mutating table error, I’ve got a serious fatal flaw in my logic.
Have you considered the multi-user implications in your logic? Two people inserting at the same time (about the same time). What happens then??? Neither will see eachothers work, neither will block — both will think «ah hah, I am first»…
anyway, you can do too much work in triggers, this may well be that time — there is nothing wrong with doing things in a more straightforward fashion (eg: using a stored procedure to implement your transaction)
PS Кроме того, не возникнет ситуация как в вопросе, где с наибольшей долей вероятности, поиск ошибки идёт не в том триггере, который эту ошибку действительно вызвал.
Администрирование баз данных, SQL, Oracle
Рекомендация: подборка платных и бесплатных курсов PR-менеджеров — https://katalog-kursov.ru/
Традиционно статья написана тезисно. Более подробное содержание можно найти в приложенном внизу статьи видео с записью лекции про триггеры Oracle.
- Общие сведения о триггерах Oracle
- DML triggers
- Псевдозаписи
- Instead of dml triggers
- Instead of triggers on Nested Table Columns of Views.
- Составные DML триггера (compound DML triggers)
- Структура составного триггера
- Основные правила определения DML триггеров
- Ограничения DML триггеров
- Ошибка мутирования таблицы ORA-04091
- Системные триггеры (System triggers)
- Триггеры уровня схемы (schema triggers)
- Триггеры уровня базы данных (database triggers)
- Instead of create triggers
- Атрибуты системных триггеров
- События срабатывания системных триггеров
- Компиляция триггеров
- Исключения в триггерах
- Порядок выполнения триггеров
- Включение/отключение триггеров
- Права для операций с триггерами
- Словари данных с информацией о триггерах
Общие сведения о триггерах
Триггер – это именованный pl/sql блок, который хранится в базе данных.
- Нельзя самому вызвать триггер, он всегда срабатывает только на определенное событие автоматически(если он enable)
- Не стоит создавать рекурсивные триггера. Т.е., например, триггер after update, в котором выполняется update той же таблицы. В этом случае триггер будет срабатывать рекурсивно до тех пор, пока не закончится память.
Классификация триггеров:
- DML trigger (на таблицу или представление)
- System trigger (на схему или базу данных)
- Conditional trigger (те, которые имеют условие when)
- Instead of trigger (dml триггер на представление или system триггер на команду create)
Зачем использовать триггеры:
- Для автоматической генерации значений виртуального поля
- Для логгирования
- Для сбора статистики
- Для изменения данных в таблицах, если в dml операции участвует представление
- Для предотвращения dml операций в какие-то определенные часы
- Для реализации сложных ограничений целостности данных, которые невозможно осуществить через описательные ограничения, установленные при создании таблиц
- Для организации всевозможных видов аудита
- Для оповещения других модулей о том, что делать в случае изменения информации в БД
- Для реализации бизнес логики
- Для организации каскадных воздействий на таблицы БД
- Для отклика на системные события в БД или схеме
где plsql_trigger_source, это такая конструкция:
Конструкции simple_dml_trigger, instead_of_dml_trigger, compound_dml_trigger и system_trigger будут приведены в соответствующих разделах статьи.
DML triggers
- DML триггеры создаются для таблиц или представлений, срабатывают при вставке, обновлении или удалении записей.
- Триггер может быть создан в другой схеме, отличной от той, где определена таблицы. В таком случае текущей схемой при выполнении триггера считается схема самого триггера.
- При операции MERGE срабатывают триггеры на изменение, вставку или удаление записей в зависимости от операции со строкой.
- Триггер – часть транзакции, ошибка в триггере откатывает операцию, изменения таблиц в триггере становятся частью транзакции.
- Если откатывается транзакция, изменения триггера тоже откатываются.
- В триггерах запрещены операторы DDL и управления транзакциями (исключения – автономные транзакции).
Конструкция simple_dml_trigger:
Где, dml_event_clause:
referencing_clause:
trigger_edition_clause:
trigger_body:
По привязанному объекту делятся на:
- На таблице
- На представлении (instead of trigger)
По событиям запуска:
- Вставка записей (insert)
- Обновление записей (update)
- Удаление записей (delete)
По области действия:
- Уровень всей команды (statement level triggers)
- Уровень записи (row level triggers)
- Составные триггеры (compound triggers)
По времени срабатывания:
- Перед выполнением операции (before)
- После выполнения операции (after)
Crossedition triggers — служат для межредакционного взаимодействия, например для переноса и трансформации данных из полей, отсутствующих в новой редакции, в другие поля.
Условные предикаты для определения операции, на которую сработал триггер:
Предикат | Описание |
---|---|
Inserting | True, если триггер сработал на операцию Insert |
Updating | True, если триггер сработал на операцию Update |
Updating(‘colum’) | True, если триггер сработал на операцию Update, которая затрагивает определенное поле |
Deleting | True, если триггер сработал на операцию Delete |
Эти предикаты могут использоваться везде, где можно использовать Boolean выражения.
Пример
CREATE OR REPLACE TRIGGER t
BEFORE
INSERT OR
UPDATE OF salary, department_id OR
DELETE
ON employees
BEGIN
CASE
WHEN INSERTING THEN
DBMS_OUTPUT.PUT_LINE('Inserting');
WHEN UPDATING('salary') THEN
DBMS_OUTPUT.PUT_LINE('Updating salary');
WHEN UPDATING('department_id') THEN
DBMS_OUTPUT.PUT_LINE('Updating department ID');
WHEN DELETING THEN
DBMS_OUTPUT.PUT_LINE('Deleting');
END CASE;
END;
Псевдозаписи
Существуют псевдозаписи, позволяющие обратиться к полям изменяемой записи и получить значения полей до изменения и значения полей после изменения. Это записи old и new. С помощью конструкции Referencing можно изменить их имена. Структура этих записей tablename%rowtype. Эти записи есть только у триггеров row level или у compound триггеров (с секциями уровня записи).
Операция срабатывания триггера | OLD.column | NEW.column |
---|---|---|
Insert | Null | Новое значение |
Update | Старое значение | Новое значение |
Delete | Старое значение | Null |
Restrictions:
- С псевдозаписями запрещены операции уровня всей записи ( :new = null;)
- Нельзя изменять значения полей записи old
- Если триггер срабатывает на delete, нельзя изменить значения полей записи new
- В триггере after нельзя изменить значения полей записи new
Instead of dml triggers
- Создаются для представлений (view) и служат для замещения DML операций своим функционалом.
- Позволяют производить операции вставки/обновления или удаления для необновляемых представлений.
Конструкция instead_of_dml_trigger:
- Это всегда триггер уровня записи (row level)
- Имеет доступ к псевдозаписям old и new, но не может изменять их
- Заменяет собой dml операцию с представлением (view)
Пример
CREATE OR REPLACE VIEW order_info AS
SELECT c.customer_id, c.cust_last_name, c.cust_first_name,
o.order_id, o.order_date, o.order_status
FROM customers c, orders o
WHERE c.customer_id = o.customer_id;
CREATE OR REPLACE TRIGGER order_info_insert
INSTEAD OF INSERT ON order_info
DECLARE
duplicate_info EXCEPTION;
PRAGMA EXCEPTION_INIT (duplicate_info, -00001);
BEGIN
INSERT INTO customers
(customer_id, cust_last_name, cust_first_name)
VALUES (
:new.customer_id,
:new.cust_last_name,
:new.cust_first_name);
INSERT INTO orders (order_id, order_date, customer_id)
VALUES (
:new.order_id,
:new.order_date,
:new.customer_id);
EXCEPTION
WHEN duplicate_info THEN
RAISE_APPLICATION_ERROR (
num=> -20107,
msg=> 'Duplicate customer or order ID');
END order_info_insert;
Instead of triggers on Nested Table Columns of Views
Можно создать триггер для вложенной в представлении таблицы. В таком триггере также присутствует дополнительная псевдозапись – parent, которая ссылается на всю запись представления (стандартные псевдозаписи old и new ссылаются только на записи вложенной таблицы)
Пример такого триггера
-- Create type of nested table element:
CREATE OR REPLACE TYPE nte
AUTHID DEFINER IS
OBJECT (
emp_id NUMBER(6),
lastname VARCHAR2(25),
job VARCHAR2(10),
sal NUMBER(8,2)
);
/
-- Created type of nested table:
CREATE OR REPLACE TYPE emp_list_ IS
TABLE OF nte;
/
-- Create view:
CREATE OR REPLACE VIEW dept_view AS
SELECT d.department_id,
d.department_name,
CAST (MULTISET (SELECT e.employee_id, e.last_name, e.job_id, e.salary
FROM employees e
WHERE e.department_id = d.department_id
)
AS emp_list_
) emplist
FROM departments d;
-- Create trigger:
CREATE OR REPLACE TRIGGER dept_emplist_tr
INSTEAD OF INSERT ON NESTED TABLE emplist OF dept_view
REFERENCING NEW AS Employee
PARENT AS Department
FOR EACH ROW
BEGIN
-- Insert on nested table translates to insert on base table:
INSERT INTO employees (
employee_id,
last_name,
email,
hire_date,
job_id,
salary,
department_id
)
VALUES (
:Employee.emp_id, -- employee_id
:Employee.lastname, -- last_name
:Employee.lastname || '@company.com', -- email
SYSDATE, -- hire_date
:Employee.job, -- job_id
:Employee.sal, -- salary
:Department.department_id -- department_id
);
END;
Запускает триггер оператор insert
INSERT INTO TABLE (
SELECT d.emplist
FROM dept_view d
WHERE department_id = 10
)
VALUES (1001, 'Glenn', 'AC_MGR', 10000);
Составные DML триггера (compound DML triggers)
Появившиеся в версии 11G эти триггера включают в одном блоке обработку всех видов DML триггеров.
Конструкция compound_dml_trigger:
Где, compound_trigger_block:
timing_point_section:
timing_point:
tps_body:
- Срабатывают такие триггера при разных событиях и в разные моменты времени (на уровне оператора или строки, при вставке/обновлении/удалении, до или после события).
- Не могут быть автономными транзакциями.
В основном используются, чтобы:
- Собирать в коллекцию строки для вставки в другую таблицу, чтобы периодически вставлять их пачкой
- Избежать ошибки мутирующей таблицы (mutating-table error)
Структура составного триггера
Может содержать переменные, которые живут на всем протяжении выполнения оператора, вызвавшего срабатывание триггера.
Такой триггер содержит следующие секции:
- Before statement
- After statement
- Before each row
- After each row
В этих триггерах нет секции инициализации, но для этих целей можно использовать секцию before statement.
Если в триггере нет ни before statement секции, ни after statement секции, и оператор не затрагивает ни одну запись, такой триггер не срабатывает.
Restrictions:
- Нельзя обращаться к псевдозаписям old, new или parent в секциях уровня выражения (before statement и after statement)
- Изменять значения полей псевдозаписи new можно только в секции before each row
- Исключения, сгенерированные в одной секции, нельзя обрабатывать в другой секции
- Если используется оператор goto, он должен указывать на код в той же секции
Пример
create or replace trigger tr_table_test_compound
for update or delete or insert on table_test
compound trigger
v_count pls_integer := 0;
before statement is
begin
dbms_output.put_line ( 'before statement' );
end before statement;
before each row is
begin
dbms_output.put_line ( 'before insert' );
end before each row;
after each row is
begin
dbms_output.put_line ( 'after insert' );
v_count := v_count + 1;
end after each row;
after statement is
begin
dbms_output.put_line ( 'after statement' );
end after statement;
end tr_table_test_compound;
Основные правила определения DML триггеров
- Update of – позволяет указать список изменяемых полей для запуска триггера
- Все условия в заголовке и When … проверяются без запуска триггера на стадии выполнения SQL
- В операторе When можно использовать только встроенные функции
- Можно делать несколько триггеров одного вида, порядок выполнения не определен по умолчанию, но его можно задать с помощью конструкции FOLLOWS TRIGGER_FIRST
- Ограничения уникальности проверяются при изменении записи, то есть после выполнения триггеров before
- Секция объявления переменных определяется словом DECLARE
- Основной блок триггера подчиняется тем же правилам, что и обычные PL/SQL блоки
Ограничения DML триггеров
- нельзя выполнять DDL statements (только в автономной транзакции)
- нельзя запускать подпрограммы с операторами контроля транзакций
- не имеет доступа к SERIALLY_REUSABLE пакетов
- размер не может превышать 32К
- нельзя декларировать переменные типа LONG и LONG RAW
Ошибка мутирования таблицы ORA-04091
Если в триггере уровня строки попытаться получить или изменить данные в целевой таблицы, то Oracle не позволит это сделать и выкинет ошибку ORA-04091 Таблица TABLE_TEST изменяется, триггер/функция может не заметить это.
Для обхода данной проблемы используются следующие приемы:
- использовать триггеры уровня операции
- автономная транзакция в триггере
- использовать сторонние структуры (коллекции уровня пакета)
- использовать COMPOUND TRIGGER
- изменение самого алгоритма с выносом функционала из триггера
Системные триггеры (System triggers)
Конструкция system_trigger:
Такие триггеры относятся или к схеме, или ко всей базе данных.
Есть несколько вариантов, в какой момент времени срабатывает системный триггер:
- До того, как будет выполнена операция (на которую срабатывает триггер)
- После того, как будет выполнена операция (на которую срабатывает триггер)
- Вместо выполнения оператора Create
Триггеры уровня схемы (schema triggers)
- Срабатывает всегда, когда пользователь-владелец схемы запускает событие (выполняет операцию), на которую должен срабатывать триггер.
- В случае, если любой другой пользователь запускает процедуру/функцию, которая вызывается с правами создателя, и в этой процедуре/функции выполняется операция, на которую создан системный триггер – этот триггер сработает.
Пример триггера
CREATE OR REPLACE TRIGGER drop_trigger
BEFORE DROP ON hr.SCHEMA
BEGIN
RAISE_APPLICATION_ERROR (
num => -20000,
msg => 'Cannot drop object');
END;
Триггеры уровня базы данных (database triggers)
- Такой триггер срабатывает когда любой пользователь БД выполняет команду, на которую создан триггер.
Пример триггера
CREATE OR REPLACE TRIGGER check_user
AFTER LOGON ON DATABASE
BEGIN
check_user;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR
(-20000, 'Unexpected error: '|| DBMS_Utility.Format_Error_Stack);
END;
Instead of create triggers
- Это триггер уровня схемы, который срабатывает на команду create и заменяет собой эту команду (т.е. вместо выполнения команды create выполняется тело триггера).
Пример триггера
CREATE OR REPLACE TRIGGER t
INSTEAD OF CREATE ON SCHEMA
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE T (n NUMBER, m NUMBER)';
END;
Атрибуты системных триггеров
Атрибут | Возвращаемое значение и тип |
---|---|
ora_client_ip_address | Varchar2 ip-адрес клиента Пример:
|
ora_database_name | Varchar2(50) имя базы данных Пример:
|
ora_des_encrypted_password | Varchar2 зашифрованный по стандарту DES пароль пользователя, который создается или изменяется Пример:
|
ora_dict_obj_name | Varchar2(30) имя объекта, над которым совершается операция DDL Пример:
|
ora_dict_obj_name_list ( name_list OUT ora_name_list_t ) |
Pls_integer количество изменненых командой объектов Name_list – список измененных командой объектов Пример:
|
ora_dict_obj_owner | Varchar2(30) владелец объекта, над которым совершается операция DDL Пример:
|
ora_dict_obj_owner_list ( owner_list OUT ora_name_list_t ) |
Pls_integer количество владельцев измененных командой объектов Owner_list – список владельцев изменных командой объектов Пример:
|
ora_dict_obj_type | Varchar2(20) тип объекта, над которым совершается операция ddl Пример:
|
ora_grantee ( user_list OUT ora_name_list_t ) |
Pls_integer количество пользователей, участвующих в операции grant User_list – список этих пользователей Пример:
|
ora_instance_num | Number номер инстанса Пример:
|
ora_is_alter_column ( column_name IN VARCHAR2 ) |
Boolean True, если указанное поле было изменено операцией alter. Иначе false Пример:
|
ora_is_creating_nested_table | Boolean true, если текущее событие – это создание nested table. Иначе false Пример:
|
ora_is_drop_column ( column_name IN VARCHAR2 ) |
Boolean true, если указанное поле удалено. Иначе false Пример:
|
ora_is_servererror ( error_number IN VARCHAR2 ) |
Boolean true, если сгенерированно исключение с номером error_number. Иначе false Пример:
|
ora_login_user | Varchar2(30) имя текущего пользователя Пример:
|
ora_partition_pos | Pls_integer в instead of trigger для create table позиция в тексте sql команды, где может быть вставлена конструкция partition Пример:
|
ora_privilege_list ( privilege_list OUT ora_name_list_t ) |
Pls_integer количество привилегий, участвующее в операции grant или revoke Privilege_list – список этих привилегий Пример:
|
ora_revokee ( user_list OUT ora_name_list_t ) |
Pls_integer количество пользователей, участвующих в операции revoke User_list – список этих пользователей Пример:
|
ora_server_error ( position IN PLS_INTEGER ) |
Number код ошибки в указанной позиции error stack, где 1 – это вершина стека Пример:
|
ora_server_error_depth | Pls_integer количество сообщений об ошибка в error stack Пример:
|
ora_server_error_msg ( position IN PLS_INTEGER ) |
Varchar2 сообщение об ошибке в указанном месте error stack Пример:
|
ora_server_error_num_params ( position IN PLS_INTEGER ) |
Pls_integer количество замещенных строк (с помощью формата %s) в указанной позиции error stack Пример:
|
ora_server_error_param ( position IN PLS_INTEGER, param IN PLS_INTEGER ) |
Varchar2 замещенный текст в сообщении об ошибке в указанной позиции error stack (возвращается param по счету замещенный текст) Пример:
|
ora_sql_txt ( sql_text OUT ora_name_list_t ) |
Pls_integer количество элементов в pl/sql коллекции sql_text. Сам параметр sql_text возвращает текст команды, на которую сработал триггер Пример:
|
ora_sysevent | Varchar2(20) название команды, на которую срабатывает триггер Пример:
|
ora_with_grant_option | Boolean true, если привилегии выдаются with grant option. Иначе false. Пример:
|
ora_space_error_info ( error_number OUT NUMBER, error_type OUT VARCHAR2, object_owner OUT VARCHAR2, table_space_name OUT VARCHAR2, object_name OUT VARCHAR2, sub_object_name OUT VARCHAR2 ) |
Boolean true, если ошибка возникает из-за нехватки места. В выходных параметрах информация об объекте. Пример:
|
События срабатывания системных триггеров
Событие | Описание | Доступные атрибуты |
---|---|---|
AFTER STARTUP | При запуске БД. Бывает только уровня БД. При ошибке пишет в системный лог. | ora_sysevent ora_login_user ora_instance_num ora_database_name |
BEFORE SHUTDOWN | Перед тем, как сервер начнет процесс останова. Бывает только уровня БД. При ошибке пишет в системный лог. | ora_sysevent ora_login_user ora_instance_num ora_database_name |
AFTER DB_ROLE_CHANGE | При запуске БД в первый раз после смены ролей from standby to primary or from primary to standby. используется только в конфигурации Data Guard,, бывает только уровня БД. |
ora_sysevent ora_login_user ora_instance_num ora_database_name |
AFTER SERVERERROR | Если случается любая ошибка (если с условием, то срабатывает только на ошибку, указанную в условии). При ошибке в теле триггера не вызывает себя рекурсивно. | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_server_error ora_is_servererror ora_space_error_info |
BEFORE ALTER
AFTER ALTER |
Если объект изменяется командой alter | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_type ora_dict_obj_name ora_dict_obj_owner ora_des_encrypted_password (for ALTER USER events) ora_is_alter_column (for ALTER TABLE events) ora_is_drop_column (for ALTER TABLE events) |
BEFORE DROP
AFTER DROP |
При удалении объекта | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_type ora_dict_obj_name ora_dict_obj_owner |
BEFORE ANALYZE
AFTER ANALYZE |
При срабатывании команды analyze | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner |
BEFORE ASSOCIATE STATISTICS
AFTER ASSOCIATE STATISTICS |
При выполнении команды associate statistics | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_dict_obj_name_list ora_dict_obj_owner_list |
BEFORE AUDIT
AFTER AUDIT BEFORE NOAUDIT AFTER NOAUDIT |
При выполнении команды audit или noaudit | ora_sysevent ora_login_user ora_instance_num ora_database_name |
BEFORE COMMENT
AFTER COMMENT |
При добавлении комментария к объекту | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner |
BEFORE CREATE
AFTER CREATE |
При создании объекта | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_type ora_dict_obj_name ora_dict_obj_owner ora_is_creating_nested_table (for CREATE TABLE events) |
BEFORE DDL
AFTER DDL |
Срабатывает на большинство команд DDL, кроме: alter database, create control file, create database. | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner |
BEFORE DISASSOCIATE STATISTICS
AFTER DISASSOCIATE STATISTICS |
При выполнении команды disassociate statistics | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_dict_obj_name_list ora_dict_obj_owner_list |
BEFORE GRANT
AFTER GRANT |
При выполнении команды grant | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_grantee ora_with_grant_option ora_privilege_list |
BEFORE LOGOFF | Срабатывает перед дисконнеком пользователя, бывает уровня схемы или БД | ora_sysevent ora_login_user ora_instance_num ora_database_name |
AFTER LOGON | Срабатывает после того, как пользователь успешно установил соединение с БД. При ошибке запрещает пользователю вход. Не действует на SYS. | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_client_ip_address |
BEFORE RENAME
AFTER RENAME |
При выполнении команды rename | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_owner ora_dict_obj_type |
BEFORE REVOKE
AFTER REVOKE |
При выполнении команды revoke | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_revokee ora_privilege_list |
AFTER SUSPEND | Срабатывает в случае, если sql команда приостанавливается по причине серверной ошибки (нехватки памяти). При этом триггер должен изменить условия таким образом, чтобы выполнение команды было возобновлено) |
ora_sysevent ora_login_user ora_instance_num ora_database_name ora_server_error ora_is_servererror ora_space_error_info |
BEFORE TRUNCATE
AFTER TRUNCATE |
При выполнении команды truncate | ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner |
Компиляция триггеров
Если во время выполнения команды create trigger произошла ошибка, триггер все равно будет создан, но будет в невалидном состоянии. При этом все попытки выполнить операцию(на которую должен срабатывать триггер) над объектом, на котором висит такой триггер, будут завершаться ошибкой. Это не относится к случаям, когда:
- Триггер создан в состоянии disabled (или переведен в такое состояние)
- Событие триггера after startup on database
- Событие триггера after logon on database или after logon on schema и происходит попытка залогиниться под пользователем System
Чтобы перекомпилировать триггер, используйте команду alter trigger.
Исключения в триггерах
В случае, если в триггере возникает исключение, вся операция откатывается (включая любые изменения, сделанные внутри триггера). Исключения из этого:
- Если событие триггера after startup on database или before shutdown on database
- Если событие триггера after logon on database и пользователь имеет привилегию administer database trigger
- Если событие триггера after logon on schema и пользователь или является владельцем схемы, или имеет привилегию alter any trigger
Порядок выполнения триггеров
Конструкция trigger_ordering_clause:
- Сначала выполняются все before statement триггера
- Потом все before each row триггера
- После все after each row триггера
- И в конце все after statement триггера
Чтобы задать явно порядок выполнения триггеров, срабатывающих в одинаковый момент времени (потому что по умолчанию такой порядок не определен), используйте конструкции follows и precedes.
Включение/отключение триггеров
Это может понадобиться, например, для загрузки большого объема информации в таблицу.
Выполнить включение/отключение триггера можно с помощью команды:
ALTER TRIGGER [schema.]trigger_name { ENABLE | DISABLE };
Чтобы включить/отключить сразу все триггеры на таблице:
ALTER TABLE table_name { ENABLE | DISABLE } ALL TRIGGERS;
Для изменения триггера можно или воспользоваться командой Create or replace trigger, или сначала удалить триггер drop trigger, а потом создать заново create trigger.
Операция alter trigger позволяет только включить/отключить триггер, скомпилировать его или переименовать.
Компиляция триггера:
alter trigger TRIGGER_NAME compile;
Права для операций с триггерами
Для работы с триггерами даже в своей схеме необходима привилегия create trigger, она дает права на создание, изменение и удаление.
grant create trigger to USER;
Для работы с триггерами во всех других схемах необходима привилегия * any trigger. Обратите внимание, что права даются отдельно на создание, изменение и удаление.
grant create any trigger to USER;
grant alter any trigger to USER;
grant drop any trigger to USER;
Для работы с системными триггерами уровня DATABASE необходима привилегия ADMINISTER DATABASE TRIGGER.
grant ADMINISTER DATABASE TRIGGER to USER;
Словари данных с информацией о триггерах:
- dba_triggers – информация о триггерах
- dba_source — код тела триггера
- dba_objects – валидность триггера
Видео-запись лекции, по материалам которой и была написана эта статья:
Множество других видео по темам Oracle можно найти на этом канале: www.youtube.com/c/MoscowDevelopmentTeam
Другие статьи по Oracle
Все о коллекциях в Oracle
������� � ���� Oracle �� ������� : ��� ���������� �������
������ 10
��� ���������� �������
��������� ����������! ���� ������ ��������
���������, ������� ������ ��� ���� � ������������ ������ comp.databases.oracle.misc
� 1999 ����. �����, ���������� ��� ����� ����, ����� ��� ������������� �
�����������������. � �������� ����������� ���������, ������� ������ �������,
����������� � ������.
������:
��� ������ ��������, ����� ���-������ �������� ���, � ��� ���� �������
���������� ������. � ������� ���������� � ���������� �������� �
���������� ������������, �� ���� ������� ��� ���� �������� ������ �������. ������
�� �������� ������? ��� �������� ����� ��������?
����� ���� �����:
� ������������ ������ ��������� �� ��� ���� (� ���������, � ������ deja news
���, ����������, ���). ����� ������� ��� ����� �� ����������� ������. � ������� ��� ������, ��� � ����� — ����� ����� ������� ���������� ������, ����� ������, � ��� ����
����. � ��������, ������ ��������� ������ ���������� ������� (��� ����� �� ������)
� ��� �� ��������…
�������� ������, �������� � ������ 1997 ����
� ���� ���� �������� ������� before insert, ��������� ���������� ���
���������� ��������� INSERT INTO table VALUES(x, x, x), �� ����
��������� INSERT INTO table (SELECT x, x, x FROM other_table), ��
������ ��������� �� ������ «ORA-04091: table is mutating, trigger/function may
not see it.»
��� �������� ����� ��������?
���������:
� ���� ���� 2 �������:
-
CREATE TABLE member (group_id NUMBER NOT NULL, unit_id NUMBER NOT NULL, carr NUMBER NOT NULL, CONSTRAINT pk_member PRIMARY KEY (group_id, unit_id) ); CREATE TABLE carr_change (group_id NUMBER NOT NULL, old_carr NUMBER NOT NULL, new_carr NUMBER NOT NULL);
��� ��������� �������, ����� ��� ������� ����� � member,
���������� ���������� ������� (unit) ������. ���� � ���� ������
�������� �������� carr, ��� � ������������, ��� ���������� ��������
������ � ������� carr_change ��� �������� ��������� �����. ����� �������,
���� ������� ������ (1, 1, 7) � ������� member, � ����������� ������
(1, 2, 6), ���������� �������� ������ (1, 7, 6) � �������
carr_change.
��� ����� ������� � �������:
-
CREATE OR REPLACE TRIGGER before_member_trig BEFORE INSERT ON member FOR EACH ROW DECLARE l_prev_carr member.carr%TYPE; BEGIN SELECT carr INTO l_prev_carr FROM member WHERE group_id = :new.group_id AND unit_id = (:new.unit_id - 1) ); IF :new.carr != l_prev_carr THEN INSERT INTO carr_change (group_id, old_carr, new_carr) VALUES (:new.group_id, l_prev_carr, :new.carr); END IF; EXCEPTION WHEN NO_DATA_FOUND THEN NULL; -- this is fine, first unit for a group WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(SQLERRM); END; /
���� ������� ������ �������� ��� ������� � ������������ VALUES, ��
�� ����������� ��� ������� � �����������. ������ ���������� �������,
ORA -04091. ��� �� ����, ������ �� �������� ����� � ��� ��� ���������?
�����:
��� ������� ��� ������ ��� �� ����� ����������� ��������.
��������� ������� ��� ������������� �������, ����������, �������� (����������), ����� �������� ������ ���������� ��� ����������
����� ������ �� ��������� insert, � ����������� �� ������� �����
� ���������� (��������: �������� insert ��� ����������� ����������� �����
���� ������ ���������, ��� ��� ������� ����������� �� ������ ������).
��������, ���� ������� ���� �����. � � ��������� ������� ��������
-
(1, 1, 11 ); (1, 2, 12 ); (1, 3, 13 );
���� ��������� ������ �������� � ����� �������, ������� ������� ������� ����
����� � ������� carr_change.
���� �� ������ �� ���������� �������� (�������, ���������� ��� �� ������,
������ ���������� ������� ������� ����� — �� �� �� ������ ���������) � ����� �������:
-
(1, 3, 13 ); (1, 2, 12 ); (1, 1, 11 );
�� ���� ������ ��������� �� �����, � ������� �� ����������� �������� (������
�� �� ����� ��� �����������������).
������� ����� �������� ���� ������ � ������������ �������
BEFORE INSERT/FOR EACH ROW, �������� ������� (��� ���� �� �����
���������������, ��������� ������� ��� �������, ������� ���������� � ��������� �����
����������� ����, ����� ��� ������ ����������� ������ � ���� �� ���������
���������� ������ ����������). ��� ������� ����� ������ ������� ��� �� ������
«����������», ����� ����������� ������� before insert/for
each row (�� ��� ����� ��� ���������� ��� �������� after insert/for each
row, ���� ��� ������� ����� ������).
��, � ��� �� ��� ������? ������������ ������� after. ��������, � ���������
��� ������� ��������� �������:
-
create or replace package member_pkg as type rArray is table of rowid index by binary_integer; rids rArray; cnt number; end; / create or replace trigger member_bi before insert on member begin member_pkg.cnt := 0; end; / create or replace trigger member_aifer after insert on member for each row begin member_pkg.cnt := member_pkg.cnt+1; member_pkg.rids( member_pkg.cnt ) := :new.rowid; end; / CREATE OR REPLACE TRIGGER member_ai after INSERT ON member DECLARE l_prev_carr member.carr%TYPE; l_rec member%rowtype; begin for i in 1 .. member_pkg.cnt loop BEGIN select * into l_rec from member where rowid = member_pkg.rids(i); SELECT carr INTO l_prev_carr FROM member WHERE group_id = l_rec.group_id AND unit_id = (l_rec.unit_id - 1 ); IF l_rec.carr != l_prev_carr THEN INSERT INTO carr_change (group_id, old_carr, new_carr) VALUES (l_rec.group_id, l_prev_carr, l_rec.carr); END IF; EXCEPTION WHEN NO_DATA_FOUND THEN dbms_output.put_line('no data'); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(SQLERRM); END; end loop; end; /
���, ��� ��������
-
insert into member select 1, 1, 11 from dual union all select 1, 2, 12 from dual union all select 1, 3, 13 from dual; select * from carr_change; rollback; insert into member select 1, 3, 13 from dual union all select 1, 2, 12 from dual union all select 1, 1, 11 from dual ; select * from carr_change;
������ ���� ������������� (��� ��) �����, ���� ���� ������
����������� � «��������» ������� (���� �� �� �� ������� �������
«����������», ��� �������������� ���� ��������� insert ���� ��
������ ���������� ��� ������������� ������������� �������� — ����
������� �� 2 ������ � ������� carr_change, � ������ — 0 �����.)
��������� � �������� ��������� ������ ������ � ����� (:new) ���������,
� �������� �������� ����� ������ ��������� �������������� �����. ���� ��
���������� ���� � ������ ��������, �������� �� ��������� �� � ��������� ��������.
����������� �.�.
����� ���� ������ ����������� ���������� ������, �������, ����? ���������� � ��������
�������� ���-�� ���������� ��� �������� ���������� ������, � ����� � �����������
after-�������� ��������� �� ��������, ������� �������� � ��������
�������� � ������ ����������
�������. ����������� ������� ����� ���� � ��� ������������ �����������, � ����� ����
������� �� �� ������ ���, ��� ������, ���� �������� ����������, ���� ��������
������������ ������.
���� ������ � ���, ��� ��������� ���������� ������ � ����������� ��������.
������-�� ��������� ���� ������ ������ ������������ ������������
�������������������� ����� ������� �� ���� PL/SQL-������. � ��� �������
������������ ������� ���� ������, ���� ���� ���������…
� ��������� �������
� ����� � ������. �������� ���������� ����� � ����� �������. ��� ���� ����� �����������. ������� �� ��������� ��
����� OpenXS Initiative � �����
��� ���� ������� � ��������� �� ����������
������ ��������.
� ���������� �����������,
�.�.
Mutating table Error Occurs when Trigger is Querying or Modifying a “Mutating Table”
ORA-04091 is a very common error that occurs with triggers if triggers are not managed properly. A full understanding of triggers will help you avoid that error. A mutating table is a table that is currently being modified by an update, delete, or insert statement. You will encounter the ORA-04091 error if you have a row trigger that reads or modifies the mutating table. For example, if your trigger contains a select statement or an update statement referencing the table it is triggering off of you will receive the error.
Another way that this error can occur is if the trigger has statements to change the primary, foreign or unique key columns of the table the trigger is triggering from. If you must have triggers on tables that have referential constraints, the workaround is to enforce the referential integrity through triggers as well.
Let’s take an example in which we have to know total number Employees with Active status after any status is updated to ‘Active’. We will see it with an example. First let us create a table and then trigger.
Create a table and insert records into that table
Here is the table definition I have created and inserted some records.
SQL> DESC TEST; Name Null? Type -------------- -------- ---------------------------- ID NUMBER NAME VARCHAR2(10) SALARY NUMBER(20) STATUS VARCHAR2(10)
SQL> SELECT * FROM TEST; ID NAME SALARY STATUS ---------- ---------- ---------- ---------- 102 Ankit 8000 Active 104 Nikhil 69000 Active 105 Rajan 18000 InActive 107 Karan 101000 Active 110 Sajal 88000 InActive 101 Ravi 89000 Active 109 Manu 777000 InActive 7 rows selected.
Now, Let’s create a Mutating Trigger (Row Level) on Update of Status column of above table.
SQL> CREATE OR REPLACE TRIGGER MutatingTrigger /*Row Level TRIGGER*/ 2 AFTER UPDATE OF STATUS ON TEST 3 FOR EACH ROW 4 DECLARE 5 V_count NUMBER; 6 BEGIN 7 Select count(*) Into V_count From TEST 8 Where STATUS='Active'; 9 DBMS_OUTPUT.PUT_LINE('Total Number of Active Records: '|| V_count); 10 END; 11 / Trigger created.
If we try to change status of any record to “Active”, Oracle will throw a Mutating Table Error as we are trying to update the records and trigger is trying to select affected records in the same trigger.
SQL> UPDATE TEST 2 SET STATUS='Active' Where ID IN(109,110); UPDATE TEST * ERROR at line 1: ORA-04091: table HR.TEST is mutating, trigger/function may not see it ORA-06512: at "HR.MUTATINGTRIGGER", line 4 ORA-04088: error during execution of trigger 'HR.MUTATINGTRIGGER
So, When you encounter an ORA-04091 error, the following error message will appear:
ORA-04091: table name is mutating, trigger/function may not see it Cause - A statement executed a Trigger or custom PL/SQL Functions. That trigger/function tried to modify or query a table that is currently being modified by the statement that fired the trigger/function.
Solution 1
Create Statement Level Trigger instead of Row Level Trigger. Row Level Triggers can’t read Mutating tables- Change Row Level Trigger to Statement Level Trigger. IF we omit the ‘For Each Row’ clause from above Trigger, it will become statement level Trigger. Let’s modify above trigger to statement level trigger.
SQL> CREATE OR REPLACE TRIGGER MutatingTrigger /*Statement Level TRIGGER*/ 2 AFTER UPDATE OF STATUS ON TEST 3 DECLARE 4 V_count NUMBER; 5 BEGIN 6 Select count(*) Into V_count From TEST 7 Where STATUS='Active'; 8 DBMS_OUTPUT.PUT_LINE('Total Number of Active Records: '|| V_count); 9 END; 10 / Trigger created.
Let’s try to fire the same Update statement now and see if you still get Mutating Error.
SQL> UPDATE TEST 2 SET STATUS='Active' Where ID IN(109,110); Total Number of Active Records: 6 2 rows updated.
SQL> SELECT * From TEST; ID NAME SALARY STATUS ---------- ---------- ---------- ---------- 102 Ankit 8000 Active 104 Nikhil 69000 Active 105 Rajan 18000 InActive 107 Karan 101000 Active 110 Sajal 88000 Active 101 Ravi 89000 Active 109 Manu 777000 Active 7 rows selected.
As you can see it works fine with Statement Level Trigger. With ‘For Each Row’, session cannot query the same table. This restriction applies to all row level triggers and hence we run into mutating table error.
Solution 2
Make the transaction independent using PRAGMA AUTONOMOUS TRANSACTION. Declare a Row level trigger as an Autonomous Transaction so that it is not in the same scope of the session issuing DML statements.
The AUTONOMOUS_TRANSACTION PRAGMA changes the way a subprogram works within a transaction. A subprogram marked with this PRAGMA can do SQL operations and commit or rollback those operations, without committing or rolling back the data in the main transaction. Autonomous transactions allow you to leave the context of the calling transaction, perform an independent transaction, and return to the calling transaction without affecting its state.
Following is the ROW Level Trigger defined as PRAGMA AUTONOMOUS TRANSACTION.
SQL> CREATE OR REPLACE TRIGGER MutatingTrigger 2 AFTER UPDATE OF STATUS ON TEST 3 FOR EACH ROW 4 DECLARE 5 V_count NUMBER; 6 PRAGMA AUTONOMOUS_TRANSACTION; 7 BEGIN 8 Select count(*) Into V_count From TEST 9 Where STATUS='Active'; 10 DBMS_OUTPUT.PUT_LINE('Total Number of Active Records: '|| V_count); 11 END; 12 / Trigger created.
Now let’s issue the Update statement again and see if now it works with ROW Level Trigger as now we have Created a Trigger with PRAGMA.
SQL> Select * From TEST; ID NAME SALARY STATUS ---------- ---------- ---------- ---------- 102 Ankit 8000 Active 104 Nikhil 69000 Active 105 Rajan 18000 InActive 107 Karan 101000 Active 110 Sajal 88000 InActive 101 Ravi 89000 Active 109 Manu 777000 InActive 7 rows selected.
SQL> UPDATE TEST 2 SET STATUS='Active' Where ID IN(109,110); Total Number of Active Records: 4 Total Number of Active Records: 4 2 rows updated.
SQL> SELECT * From TEST; ID NAME SALARY STATUS ---------- ---------- ---------- ---------- 102 Ankit 8000 Active 104 Nikhil 69000 Active 105 Rajan 18000 InActive 107 Karan 101000 Active 110 Sajal 88000 Active 101 Ravi 89000 Active 109 Manu 777000 Active 7 rows selected.
By defining Row Level trigger as an AUTONOMOUS TRANSACTION, we got rid of Mutating table error but result is not correct. Updated records are not getting reflected. So one has to be very careful while using this approach.
Solution 3
Avoid Mutating Error Using Compound Trigger. In Oracle 11g, Oracle has made is much easier by introducing Compound Trigger. A compound trigger allows code for one or more timing points for a specific object to be combined into a single trigger. The individual timing points can share a single global declaration section, whose state is maintained for the lifetime of the statement. Once a statement ends, due to successful completion or an error, the trigger state is cleaned up.
Let’s create a Compound Trigger to resolve Mutating Table Error.
SQL> CREATE OR REPLACE TRIGGER MutatingTrigger 2 FOR UPDATE 3 ON TEST 4 COMPOUND TRIGGER 5 /*Declaration Section*/ 6 V_count NUMBER; 7 8 AFTER EACH ROW IS 9 BEGIN 10 DBMS_OUTPUT.PUT_LINE('Record Updated'); 11 END AFTER EACH ROW; 12 13 AFTER STATEMENT IS 14 BEGIN 15 Select count(*) Into V_count 16 From TEST Where STATUS = 'Active'; 17 DBMS_OUTPUT.PUT_LINE('Total Number of Active Records: '|| V_count); 18 END AFTER STATEMENT; 19 20 END MutatingTrigger; 21 / Trigger created.
Now let’s issue the Update statement and see how it works with Compound Trigger.
SQL> SELECT * From TEST; ID NAME SALARY STATUS ---------- ---------- ---------- ---------- 102 Ankit 8000 Active 104 Nikhil 69000 Active 105 Rajan 18000 InActive 107 Karan 101000 Active 110 Sajal 88000 InActive 101 Ravi 89000 Active 109 Manu 777000 InActive 7 rows selected.
SQL> UPDATE TEST 2 SET STATUS='Active' Where ID IN(109,110); Record Updated Record Updated Total Number of Active Records: 6 2 rows updated.
SQL> SELECT * From TEST; ID NAME SALARY STATUS ---------- ---------- ---------- ---------- 102 Ankit 8000 Active 104 Nikhil 69000 Active 105 Rajan 18000 InActive 107 Karan 101000 Active 110 Sajal 88000 Active 101 Ravi 89000 Active 109 Manu 777000 Active 7 rows selected.
Here we get correct result without getting any Mutating Table error.
Example Workaround for ORA-4091 Error
The following is the sample work-around that used the EMP table.
1. Create the package to define the objects that will house the data.
create or replace package emp_pkg as type emp_tab_type is table of rowid index by binary_integer; emp_tab emp_tab_type; emp_index binary_integer; end emp_pkg; /
2. Create the table level trigger to reset the index BEFORE any changes take place.
create or replace trigger emp_bef_stm_all before insert or update or delete on emp begin --Remember to reset the pl/sql table before each statement emp_pkg.emp_index := 0; end; /
3. The following Row level trigger will fire for each row being modified and copy the RowID for each row to the EMP_TAB object defined within the package.
create or replace trigger emp_aft_row_all after insert or update or delete on emp for each row begin --Store the rowid of updated record into global pl/sql table emp_pkg.emp_index := emp_pkg.emp_index + 1; emp_pkg.emp_tab(emp_pkg.emp_index) := :new.rowid; end; /
4. The following table level trigger will fire AFTER the others and will print out each RowID affected.
create or replace trigger emp_aft_stm_all after insert or update or delete on emp begin for i in 1 .. emp_pkg.emp_index loop -- Re-process the updated records. --There is no restriction here. dbms_output.put_line(emp_pkg.emp_tab(i)); end loop; emp_pkg.emp_index := 0; end; /
5. In order to see how this work from SQL*Plus, the output must be turned on.
This first example, displays the RowIDs for Department 20 then issues a DELETE command. The trigger should fire and display the same RowIDs to confirm it is working. (The RowIDs will vary from machine to machine.)
SQL> SELECT ROWID, DEPTNO FROM EMP WHERE DEPTNO = 20; ROWID DEPTNO ------------------ ---------- AAAM4iAAEAAAAG+AAA 20 AAAM4iAAEAAAAG+AAD 20 AAAM4iAAEAAAAG+AAH 20 AAAM4iAAEAAAAG+AAK 20 AAAM4iAAEAAAAG+AAM 20 SQL> DELETE EMP WHERE DEPTNO = 20; AAAM4iAAEAAAAG+AAA AAAM4iAAEAAAAG+AAD AAAM4iAAEAAAAG+AAH AAAM4iAAEAAAAG+AAK AAAM4iAAEAAAAG+AAM
This second example, displays the RowIDs for Department 10 then issues an UPDATE command. The trigger should fire and display the same RowIDs to confirm it is working. (The RowIDs will vary from machine to machine.)
SQL> SELECT ROWID, DEPTNO FROM EMP WHERE DEPTNO=10; ROWID DEPTNO ------------------ ---------- AAAM4iAAEAAAAG+AAG 10 AAAM4iAAEAAAAG+AAI 10 AAAM4iAAEAAAAG+AAN 10 SQL> UPDATE EMP SET JOB='Test' WHERE DEPTNO=10; AAAM4iAAEAAAAG+AAG AAAM4iAAEAAAAG+AAI AAAM4iAAEAAAAG+AAN 3 rows updated.
6. To rollback the changes, issue the Rollback command.
SQL> ROLLBACK; Rollback complete.