Triggers are procedures that are stored in the database and implicitly run, or fired, when something happens. For more information, visit the chapter of Using Triggers in the Oracle Developer's Guide.
Before a trigger can be created, the user SYS must
run a SQL script commonly called CATPROC.SQL (or DBMSSTDX.SQL). The exact name and
location of this script depend on your operating system.
To create a trigger in your own schema on a table in your own schema or on
your own schema (SCHEMA), you must have the CREATE
TRIGGER privilege. | |
To create a trigger in any schema on a table in any schema, or on another
user's schema (schema.SCHEMA), you must
have the CREATE ANY TRIGGER
privilege. | |
In addition to the preceding privileges, to create a trigger on DATABASE,
you must have the ADMINISTER DATABASE TRIGGER
system privilege. |
If the trigger issues SQL statements or calls procedures or functions, then the owner of the trigger must have the privileges necessary to perform these operations. These privileges must be granted directly to the owner rather than acquired through roles.
When you create a trigger, Oracle enables it automatically. You
can subsequently disable and enable a trigger with the DISABLE and ENABLE
clause of the ALTER TRIGGER or ALTER TABLE
statement.
create_trigger::=

You can use triggers in a number of ways to customize information management in an Oracle database. For example, triggers are commonly used to:
| Provide sophisticated auditing | |
| Prevent invalid transactions | |
| Enforce referential integrity (either those actions not supported by declarative integrity constraints or across nodes in a distributed database) | |
| Enforce complex business rules | |
| Enforce complex security authorizations | |
| Provide transparent event logging | |
| Automatically generate derived column values | |
| Enable building complex views that are updatable | |
| Track system events |
Use the following guidelines when designing your triggers:
| Use triggers to guarantee that when a specific operation is performed, related actions are performed. | |
| Do not define triggers that duplicate the functionality already built into Oracle. For example, do not define triggers to enforce data integrity rules that can be easily enforced using declarative integrity constraints. | |
| Limit the size of triggers. If the logic for your trigger requires much more than 60 lines of PL/SQL code, then it is better to include most of the code in a stored procedure and call the procedure from the trigger. | |
| Use triggers only for centralized, global operations that should be fired for the triggering statement, regardless of which user or database application issues the statement. | |
Do not create recursive triggers. For
example, creating an AFTER UPDATE statement
trigger on the Emp_tab table that itself issues an UPDATE
statement on Emp_tab, causes the trigger to fire recursively
until it has run out of memory. | |
Use triggers on DATABASE judiciously. They are executed for every
user every time the event occurs on which the
trigger is created. |
The body of a trigger can contain DML SQL statements. It can also
contain SELECT statements, but they must be SELECT... INTO...
statements or the SELECT statement in the definition of a cursor.
DDL statements are not allowed in the body of a trigger. Also, no
transaction control statements are allowed in a trigger. ROLLBACK, COMMIT,
and SAVEPOINT cannot be used.For system triggers, {CREATE/ALTER/DROP}
TABLE statements and ALTER...COMPILE are
allowed.
Examples
This example creates a BEFORE statement trigger named
emp_permit_changes in the schema hr. You would write
such a trigger to place restrictions on DML statements issued on this table
(such as when such statements could be issued).
CREATE TRIGGER hr.emp_permit_changes
BEFORE
DELETE OR INSERT OR UPDATE
ON hr.employees
< pl/sql block >
Oracle fires this trigger whenever a DELETE, INSERT,
or UPDATE statement affects the employees table in the
schema hr. The trigger emp_permit_changes is a BEFORE
statement trigger, so Oracle fires it once before executing the triggering
statement.
This example creates a BEFORE row trigger named salary_check
in the schema scott. The PL/SQL block might specify, for example,
that the employee's salary must fall within the established salary range for the
employee's job:
CREATE TRIGGER hr.salary_check
BEFORE INSERT OR UPDATE OF salary, job_id ON hr.employees
FOR EACH ROW
WHEN (new.job_id <> 'AD_VP')
< pl/sql_block >
Oracle fires this trigger whenever one of the following statements is issued:
An INSERT statement that adds rows to
the employees table
An | UPDATE statement that changes
values of the salary or job_id columns of the employees
table |
salary_check is a BEFORE row trigger, so
Oracle fires it before changing each row that is updated by the UPDATE
statement or before adding each row that is inserted by the INSERT
statement.
salary_check has a trigger restriction that prevents
it from checking the salary of the administrative vice president (AD_VP).
You could create the salary_check trigger described
in the preceding example by calling a procedure instead of providing the trigger
body in a PL/SQL block. Assume you have defined a procedure hr.salary_check,
which verifies that an employee's salary is in an appropriate range. Then you
could create the trigger salary_check as follows:
CREATE TRIGGER hr.salary_check BEFORE INSERT OR UPDATE OF salary, job_id ON hr.employees FOR EACH ROW WHEN (new.job_id <> 'AD_VP') CALL check_sal(:new.job_id, :new.salary, :new.last_name);
The procedure check_sal could be implemented in
PL/SQL, C, or Java. Also, you can specify :OLD values in the CALL
clause instead of :NEW values.
This example creates a trigger to log all errors. The hypothetical
PL/SQL block does some special processing for a particular error (invalid logon,
error number 1017). This trigger is an AFTER statement trigger, so
it is fired after an unsuccessful statement execution (such as unsuccessful
logon).
CREATE TRIGGER log_errors AFTER SERVERERROR ON DATABASE
BEGIN
IF (IS_SERVERERROR (1017)) THEN
<special processing of logon error>
ELSE
<log error number>
END IF;
END;This example creates an AFTER statement trigger on
any DDL statement CREATE. Such a trigger can be used to audit the
creation of new data dictionary objects in your schema.
CREATE TRIOGGER audit_db_object AFTER CREATE
ON SCHEMA
< pl/sql_block >
Use the following SQL statement to create two tables call emp_tab and dept.
CREATE TABLE DEPT_TAB ( DEPTNO NUMBER(2) NOT NULL, DNAME VARCHAR2(14 byte), LOC VARCHAR2(13 byte), CONSTRAINT PK_DEPT PRIMARY KEY(DEPTNO) ); CREATE TABLE EMP_TAB ( EMPNO NUMBER(4) NOT NULL, ENAME VARCHAR2(10 byte), JOB VARCHAR2(9 byte), MGR NUMBER(4), HIREDATE DATE, SAL NUMBER(7, 2), COMM NUMBER(7, 2), DEPTNO NUMBER(2), CONSTRAINT FK_DEPTNO FOREIGN KEY(DEPTNO) REFERENCES DEPT_TAB(DEPTNO), CONSTRAINT PK_EMP PRIMARY KEY(EMPNO) );
Suppose that you want to audit the activities with the table, you can set up the following data structures before setting up a trigger:
CREATE TABLE Audit_table ( Seq NUMBER, User_at VARCHAR2(10), Time_now DATE, Term VARCHAR2(10), Job VARCHAR2(10), Proc VARCHAR2(10), enum NUMBER); CREATE SEQUENCE Audit_seq; CREATE TABLE Audit_table_values ( Seq NUMBER, Dept NUMBER, Dept1 NUMBER, Dept2 NUMBER);
Now you're ready to set up the following trigger.
CREATE OR REPLACE TRIGGER Audit_emp
AFTER INSERT OR UPDATE OR DELETE ON Emp_tab
FOR EACH ROW
DECLARE
Time_now DATE;
Terminal CHAR(10);
BEGIN
-- get current time, and the terminal of the user:
Time_now := SYSDATE;
Terminal := USERENV('TERMINAL');
-- record new employee primary key
IF INSERTING THEN
INSERT INTO Audit_table
VALUES (Audit_seq.NEXTVAL, User, Time_now,
Terminal, 'Emp_tab', 'INSERT', :new.Empno);
-- record primary key of the deleted row:
ELSIF DELETING THEN
INSERT INTO Audit_table
VALUES (Audit_seq.NEXTVAL, User, Time_now,
Terminal, 'Emp_tab', 'DELETE', :old.Empno);
-- for updates, record the primary key
-- of the row being updated:
ELSE
INSERT INTO Audit_table
VALUES (audit_seq.NEXTVAL, User, Time_now,
Terminal, 'Emp_tab', 'UPDATE', :old.Empno);
-- and for SAL and DEPTNO, record old and new values:
IF UPDATING ('SAL') THEN
INSERT INTO Audit_table_values
VALUES (Audit_seq.CURRVAL, 'SAL',
:old.Sal, :new.Sal);
ELSIF UPDATING ('DEPTNO') THEN
INSERT INTO Audit_table_values
VALUES (Audit_seq.CURRVAL, 'DEPTNO',
:old.Deptno, :new.DEPTNO);
END IF;
END IF;
END;
Although triggers can be written to enforce many of the same rules supported by Oracle's declarative integrity constraint features, triggers should only be used to enforce complex business rules that cannot be defined using standard integrity constraints.
For example, triggers can be used to enforce:
UPDATE and DELETE SET NULL,
and UPDATE and DELETE SET DEFAULT
referential actions. | |
| Referential integrity when the parent and child tables are on different nodes of a distributed database. | |
Complex check constraints not definable using the expressions allowed in a
CHECK constraint. |
The following trigger guarantees that before an INSERT
or UPDATE statement affects a foreign key value, the corresponding
value exists in the parent key. The mutating table exception included in the
example below allows this trigger to be used with the UPDATE_SET_DEFAULT
and UPDATE_CASCADE triggers. This exception can be removed if this
trigger is used alone.
CREATE OR REPLACE TRIGGER Emp_dept_check
BEFORE INSERT OR UPDATE OF Deptno ON Emp_tab
FOR EACH ROW WHEN (new.Deptno IS NOT NULL)
-- Before a row is inserted, or DEPTNO is updated in the Emp_tab
-- table, fire this trigger to verify that the new foreign
-- key value (DEPTNO) is present in the Dept_tab table.
DECLARE
Dummy INTEGER; -- used for cursor fetch below
Invalid_department EXCEPTION;
Valid_department EXCEPTION;
Mutating_table EXCEPTION;
PRAGMA EXCEPTION_INIT (Mutating_table, -4091);
-- Cursor used to verify parent key value exists. If
-- present, lock parent key's row so it can't be
-- deleted by another transaction until this
-- transaction is committed or rolled back.
CURSOR Dummy_cursor (Dn NUMBER) IS
SELECT Deptno FROM Dept_tab
WHERE Deptno = Dn
FOR UPDATE OF Deptno;
BEGIN
OPEN Dummy_cursor (:new.Deptno);
FETCH Dummy_cursor INTO Dummy;
-- Verify parent key. If not found, raise user-specified
-- error number and message. If found, close cursor
-- before allowing triggering statement to complete:
IF Dummy_cursor%NOTFOUND THEN
RAISE Invalid_department;
ELSE
RAISE valid_department;
END IF;
CLOSE Dummy_cursor;
EXCEPTION
WHEN Invalid_department THEN
CLOSE Dummy_cursor;
Raise_application_error(-20000, 'Invalid Department'
|| ' Number' || TO_CHAR(:new.deptno));
WHEN Valid_department THEN
CLOSE Dummy_cursor;
WHEN Mutating_table THEN
NULL;
END;
The following trigger is defined on the DEPT_TAB
table to enforce the UPDATE and DELETE RESTRICT
referential action on the primary key of the DEPT_TAB table:
CREATE OR REPLACE TRIGGER Dept_restrict
BEFORE DELETE OR UPDATE OF Deptno ON Dept_tab
FOR EACH ROW
-- Before a row is deleted from Dept_tab or the primary key
-- (DEPTNO) of Dept_tab is updated, check for dependent
-- foreign key values in Emp_tab; rollback if any are found.
DECLARE
Dummy INTEGER; -- used for cursor fetch below
Employees_present EXCEPTION;
employees_not_present EXCEPTION;
-- Cursor used to check for dependent foreign key values.
CURSOR Dummy_cursor (Dn NUMBER) IS
SELECT Deptno FROM Emp_tab WHERE Deptno = Dn;
BEGIN
OPEN Dummy_cursor (:old.Deptno);
FETCH Dummy_cursor INTO Dummy;
-- If dependent foreign key is found, raise user-specified
-- error number and message. If not found, close cursor
-- before allowing triggering statement to complete.
IF Dummy_cursor%FOUND THEN
RAISE Employees_present; -- dependent rows exist
ELSE
RAISE Employees_not_present; -- no dependent rows
END IF;
CLOSE Dummy_cursor;
EXCEPTION
WHEN Employees_present THEN
CLOSE Dummy_cursor;
Raise_application_error(-20001, 'Employees Present in'
|| ' Department ' || TO_CHAR(:old.DEPTNO));
WHEN Employees_not_present THEN
CLOSE Dummy_cursor;
END;
The following trigger is defined on the DEPT_TAB
table to enforce the UPDATE and DELETE SET
NULL referential action on the primary key of the DEPT_TAB
table:
CREATE OR REPLACE TRIGGER Dept_set_null
AFTER DELETE OR UPDATE OF Deptno ON Dept_tab
FOR EACH ROW
-- Before a row is deleted from Dept_tab or the primary key
-- (DEPTNO) of Dept_tab is updated, set all corresponding
-- dependent foreign key values in Emp_tab to NULL:
BEGIN
IF UPDATING AND :OLD.Deptno != :NEW.Deptno OR DELETING THEN
UPDATE Emp_tab SET Emp_tab.Deptno = NULL
WHERE Emp_tab.Deptno = :old.Deptno;
END IF;
END;
The following trigger on the DEPT_TAB table enforces
the DELETE CASCADE referential action on the primary
key of the DEPT_TAB table:
CREATE OR REPLACE TRIGGER Dept_del_cascade
AFTER DELETE ON Dept_tab
FOR EACH ROW
-- Before a row is deleted from Dept_tab, delete all
-- rows from the Emp_tab table whose DEPTNO is the same as
-- the DEPTNO being deleted from the Dept_tab table:
BEGIN
DELETE FROM Emp_tab
WHERE Emp_tab.Deptno = :old.Deptno;
END;
The following trigger ensures that if a department number is
updated in the Dept_tab table, then this change is propagated to
dependent foreign keys in the Emp_tab table:
-- Generate a sequence number to be used as a flag for
-- determining if an update has occurred on a column:
CREATE SEQUENCE Update_sequence
INCREMENT BY 1 MAXVALUE 5000
CYCLE;
CREATE OR REPLACE PACKAGE Integritypackage AS
Updateseq NUMBER;
END Integritypackage;
CREATE OR REPLACE PACKAGE BODY Integritypackage AS
END Integritypackage;
-- create flag col:
ALTER TABLE Emp_tab ADD Update_id NUMBER; .
CREATE OR REPLACE TRIGGER Dept_cascade1 BEFORE UPDATE OF Deptno ON Dept_tab
DECLARE
Dummy NUMBER;
-- Before updating the Dept_tab table (this is a statement
-- trigger), generate a new sequence number and assign
-- it to the public variable UPDATESEQ of a user-defined
-- package named INTEGRITYPACKAGE:
BEGIN
SELECT Update_sequence.NEXTVAL
INTO Dummy
FROM dual;
Integritypackage.Updateseq := Dummy;
END;
CREATE OR REPLACE TRIGGER Dept_cascade2 AFTER DELETE OR UPDATE
OF Deptno ON Dept_tab FOR EACH ROW
-- For each department number in Dept_tab that is updated,
-- cascade the update to dependent foreign keys in the
-- Emp_tab table. Only cascade the update if the child row
-- has not already been updated by this trigger:
BEGIN
IF UPDATING THEN
UPDATE Emp_tab
SET Deptno = :new.Deptno,
Update_id = Integritypackage.Updateseq --from 1st
WHERE Emp_tab.Deptno = :old.Deptno
AND Update_id IS NULL;
/* only NULL if not updated by the 3rd trigger
fired by this same triggering statement */
END IF;
IF DELETING THEN
-- Before a row is deleted from Dept_tab, delete all
-- rows from the Emp_tab table whose DEPTNO is the same as
-- the DEPTNO being deleted from the Dept_tab table:
DELETE FROM Emp_tab
WHERE Emp_tab.Deptno = :old.Deptno;
END IF;
END;
CREATE OR REPLACE TRIGGER Dept_cascade3 AFTER UPDATE OF Deptno ON Dept_tab
BEGIN UPDATE Emp_tab
SET Update_id = NULL
WHERE Update_id = Integritypackage.Updateseq;
END;
Triggers can enforce integrity rules other than referential integrity. For example, this trigger performs a complex check before allowing the triggering statement to run. Set up the following data structures for the example to work:
CREATE TABLE Salgrade ( Grade NUMBER, Losal NUMBER, Hisal NUMBER, Job_classification NUMBER)
Now you're ready to create the trigger.
CREATE OR REPLACE TRIGGER Salary_check
BEFORE INSERT OR UPDATE OF Sal, Job ON Emp99
FOR EACH ROW
DECLARE
Minsal NUMBER;
Maxsal NUMBER;
Salary_out_of_range EXCEPTION;
BEGIN
/* Retrieve the minimum and maximum salary for the
employee's new job classification from the SALGRADE
table into MINSAL and MAXSAL: */
SELECT Minsal, Maxsal INTO Minsal, Maxsal FROM Salgrade
WHERE Job_classification = :new.Job;
/* If the employee's new salary is less than or greater
than the job classification's limits, the exception is
raised. The exception message is returned and the
pending INSERT or UPDATE statement that fired the
trigger is rolled back:*/
IF (:new.Sal < Minsal OR :new.Sal > Maxsal) THEN
RAISE Salary_out_of_range;
END IF;
EXCEPTION
WHEN Salary_out_of_range THEN
Raise_application_error (-20300,
'Salary '||TO_CHAR(:new.Sal)||' out of range for '
||'job classification '||:new.Job
||' for employee '||:new.Ename);
WHEN NO_DATA_FOUND THEN
Raise_application_error(-20322,
'Invalid Job Classification '
||:new.Job_classification);
END;
Back to CS643 schedule