Trigger in Oracle
Modern relational database systems, like Oracle Database, have the essential capacity of implementing triggers, which enables the codification and automatic enforcement of business rules. A trigger is a particular kind of stored program unit, often written in PL/SQL, that “fires,” or runs automatically, in response to predetermined database events, usually Data Manipulation Language (DML) statements like INSERT
, UPDATE
, or DELETE
. For the user carrying out the DML operation, the triggers’ execution is transparent.
The timing (BEFORE or AFTER) and execution scope (Row-Level or Statement-Level) of triggers can be used to categorise them. Common in auditing systems, row-level triggers run once for each row impacted by a DML statement. Statement-level triggers only run once for the whole DML statement, regardless of the number of rows they impact.
The primary DML trigger types depend on the timing in relation to the transaction that started:
DML Trigger Timing and Types
Triggers can be set to occur immediately before or after the DML event. To facilitate conditional logic and data manipulation within row-level triggers, Oracle provides pseudo-records named :OLD
and :NEW
. The :OLD
pseudo-record holds the values of the columns prior to the DML operation (available for UPDATE
and DELETE
), while the :NEW
pseudo-record holds the values the row will contain after the DML (available for INSERT
and UPDATE
). Note that these pseudo-records must be preceded by a colon (:
) when referenced inside the PL/SQL body of the trigger.
Before Insert Trigger
A Before Insert Trigger executes before a new row is permanently added to the table. This timing is essential if you need to set the value of a column in the inserted row based on logic defined in the trigger body.
Common Use Case: Automatically populating audit columns (like creation date or default values) or deriving column values before the row is finalized.
In this context, only the :NEW
pseudo-record is populated, and its values can be modified.
Code Example (Conceptual Setup and Before Insert):
First, define a sample table (EMPLOYEES) and a log table (EMPLOYEE_LOG) for tracking changes:
-- Setup Tables
CREATE TABLE EMPLOYEES (
emp_id NUMBER PRIMARY KEY,
emp_name VARCHAR2(50),
salary NUMBER(10, 2),
created_date DATE,
modified_date DATE
);
/
CREATE TABLE EMPLOYEE_LOG (
log_id NUMBER GENERATED ALWAYS AS IDENTITY,
emp_id NUMBER,
operation_type VARCHAR2(10),
old_salary NUMBER(10, 2),
new_salary NUMBER(10, 2),
audit_date DATE
);
/
-- Before Insert Trigger (Populating CREATED_DATE)
CREATE OR REPLACE TRIGGER EMP_BIU_ROW
BEFORE INSERT OR UPDATE ON EMPLOYEES
FOR EACH ROW
BEGIN
-- Only set created_date if inserting OR if it is not already specified
IF INSERTING THEN
IF :NEW.created_date IS NULL THEN
:NEW.created_date := SYSDATE;
END IF;
END IF;
-- Always set modified_date on insert or update
IF INSERTING OR UPDATING THEN
:NEW.modified_date := SYSDATE;
END IF;
END;
/
-- Output: Trigger created.
This single trigger is a combination of Before Insert and Before Update logic. When inserting, if created_date
is null, the trigger explicitly sets the :NEW.created_date
to SYSDATE
.
After Insert Trigger
An After Insert Trigger executes only after the row has been successfully inserted (and has therefore passed any declarative referential integrity constraints).
Common Use Case: Auditing, logging the successful action, or replicating the change asynchronously to another table.
Both :NEW
and :OLD
pseudo-records are available, but only :NEW
contains meaningful data related to the inserted row.
Code Example (After Insert Logging, integrated with the above BIU trigger):
-- After Insert Trigger (Logging insertion to EMPLOYEE_LOG)
CREATE OR REPLACE TRIGGER EMP_AFI_LOG
AFTER INSERT ON EMPLOYEES
FOR EACH ROW
BEGIN
INSERT INTO EMPLOYEE_LOG (emp_id, operation_type, new_salary, audit_date)
VALUES (:NEW.emp_id, 'INSERT', :NEW.salary, SYSDATE);
END;
/
-- Output: Trigger created.
-- Test Insert:
INSERT INTO EMPLOYEES (emp_id, emp_name, salary) VALUES (101, 'JILL', 60000);
COMMIT;
-- Output: 1 row created. Commit complete.
-- Check data and audit log output:
SELECT emp_id, created_date, modified_date FROM EMPLOYEES WHERE emp_id = 101;
-- Output (dates will reflect current SYSDATE/time):
-- EMP_ID CREATED_DATE MODIFIED_DATE
-- 101 [SYSDATE] [SYSDATE]
SELECT emp_id, operation_type, new_salary FROM EMPLOYEE_LOG WHERE emp_id = 101;
-- Output:
-- EMP_ID OPERATION_TYPE NEW_SALARY
-- 101 INSERT 60000
Before Update Trigger
A Before Update Trigger executes before the existing row is modified. This type of trigger is highly flexible because both the original (:OLD
) and proposed new values (:NEW
) are available and modifiable.
Common Use Case: Implementing complex validation rules, ensuring specific columns are updated only under certain conditions (preventing changes), or performing conditional auditing before the change is finalized.
Code Example (Before Update Validation/Derivation):
We use the previous EMP_BIU_ROW
trigger definition to illustrate update functionality. When an update occurs, it guarantees modified_date
is updated to SYSDATE
.
We can add logic to restrict updates to the salary
column only if the new salary is higher than the old salary, or log the change if it is lower:
-- Before Update Trigger (Conditional check based on old vs new salary)
CREATE OR REPLACE TRIGGER EMP_SALARY_CHECK
BEFORE UPDATE OF salary ON EMPLOYEES
FOR EACH ROW
WHEN (NEW.salary < OLD.salary) -- Note: No colon needed in WHEN clause
BEGIN
-- Log a warning or raise an application error if salary is lowered
-- (This logic uses the audit log, typically this might raise an error)
RAISE_APPLICATION_ERROR(-20003, 'Salary cannot be lowered via direct update.');
END;
/
-- Output: Trigger created.
-- Test Update Attempt (Lowers salary):
UPDATE EMPLOYEES SET salary = 50000 WHERE emp_id = 101;
-- Output:
-- ERROR at line 1: ORA-20003: Salary cannot be lowered via direct update.
After Update Trigger
The row must successfully insert (and hence pass any declarative referential integrity requirements) before an After Insert Trigger can be executed.
Common Use Case: There are pseudo-records with the suffixes :NEW
and :OLD
, but only :NEW has useful information about the inserted row.
Code Example (After Update Logging):
-- After Update Trigger (Logging salary changes)
CREATE OR REPLACE TRIGGER EMP_AFU_LOG
AFTER UPDATE OF salary ON EMPLOYEES
FOR EACH ROW
BEGIN
-- Log the old and new salaries only if they are different (recommended practice)
IF :OLD.salary <> :NEW.salary THEN
INSERT INTO EMPLOYEE_LOG (emp_id, operation_type, old_salary, new_salary, audit_date)
VALUES (:NEW.emp_id, 'UPDATE', :OLD.salary, :NEW.salary, SYSDATE);
END IF;
END;
/
-- Output: Trigger created.
-- Test successful update (increases salary):
UPDATE EMPLOYEES SET salary = 70000 WHERE emp_id = 101;
COMMIT;
-- Output: 1 row updated. Commit complete.
-- Check audit log output:
SELECT operation_type, old_salary, new_salary FROM EMPLOYEE_LOG WHERE operation_type = 'UPDATE';
-- Output:
-- OPERATION_TYPE OLD_SALARY NEW_SALARY
-- UPDATE 60000 70000
Before Delete Trigger
A Before Delete Trigger executes just prior to the row being deleted.
Common Use Cases: Carrying out conditional auditing prior to the change being finalised, implementing intricate validation rules, or making sure particular columns are updated only in specific circumstances (preventing changes).
In this context, only the :OLD
pseudo-record is available, as the :NEW
row will cease to exist.
Code Example (Before Delete Security Check):
-- Before Delete Trigger (Preventing deletion on weekends, conceptual example adapted from )
CREATE OR REPLACE TRIGGER EMP_BFD_SECURITY
BEFORE DELETE ON EMPLOYEES
BEGIN
-- This is a statement-level trigger (FOR EACH ROW clause omitted)
IF TO_CHAR(SYSDATE, 'DY') IN ('SAT', 'SUN') THEN
RAISE_APPLICATION_ERROR(-20004, 'Deletions not allowed on weekends.');
END IF;
END;
/
-- Output: Trigger created.
After Delete Trigger
Once the row alteration attempt has been successful, an After Update Trigger is executed.
Common Use Case: Auditing modifications by recording both the :OLD
and NEW values in a different table.
Code Example (After Delete Logging):
-- After Delete Trigger (Logging deleted records)
CREATE OR REPLACE TRIGGER EMP_AFD_LOG
AFTER DELETE ON EMPLOYEES
FOR EACH ROW
BEGIN
INSERT INTO EMPLOYEE_LOG (emp_id, operation_type, old_salary, audit_date)
VALUES (:OLD.emp_id, 'DELETE', :OLD.salary, SYSDATE);
END;
/
-- Output: Trigger created.
-- Test deletion:
DELETE FROM EMPLOYEES WHERE emp_id = 101;
COMMIT;
-- Output: 1 row deleted. Commit complete.
-- Check audit log output:
SELECT operation_type, old_salary FROM EMPLOYEE_LOG WHERE operation_type = 'DELETE';
-- Output:
-- OPERATION_TYPE OLD_SALARY
-- DELETE 70000
Trigger Management Commands
To manage the existence and state of triggers, Oracle offers a number of SQL commands that let developers and administrators enable, disable, or completely delete them.
Disable Trigger (Specific) and Enable Trigger (Specific)
By utilising the ALTER TRIGGER
command in conjunction with the ENABLE
or DISABLE
keywords, individual triggers can be explicitly enabled or disabled. Either owning the trigger or possessing the ALTER ANY TRIGGER
system privilege are necessary for this.
Disabling a Specific Trigger:
ALTER TRIGGER EMP_AFI_LOG DISABLE;
-- Output: Trigger altered.
Enabling a Specific Trigger:
ALTER TRIGGER EMP_AFI_LOG ENABLE;
-- Output: Trigger altered.
The database won’t fire a trigger that is disabled, even if the triggering circumstance is met.
Disable All Triggers and Enable All Triggers
In replication circumstances or when handling huge data loads (loading, copying, or moving data), it is frequently required to temporarily manage all triggers connected to a table. During large loads, performance can be significantly enhanced by turning off triggers. Disabling triggers on target tables is essential for replication technologies such as Oracle GoldenGate because the replication system already sends the output of trigger operations from the database, preventing duplicate changes.
The ALTER TABLE
command with the ALL TRIGGERS
clause is used to do this management.
Disable All Triggers:
This command disables all triggers associated with a specified table.
ALTER TABLE EMPLOYEES DISABLE ALL TRIGGERS;
-- Output: Table altered.
Enable All Triggers:
This command enables all triggers associated with a specified table.
ALTER TABLE EMPLOYEES ENABLE ALL TRIGGERS;
-- Output: Table altered.
You need to have the ALTER ANY TABLE
system privilege or own the table in order to utilise the ALTER TABLE
command for trigger management. If triggers were disabled during a data load, the administrator will need to manually carry out the data modification that would have been carried out by the disabled triggers.
Drop Trigger
A trigger definition can be permanently deleted from the database by using the Drop Trigger command. After being dumped, the trigger is no longer present and will not react to events.
You need to either own the trigger or have the system privilege DROP ANY TRIGGER
in order to drop it.
Code Example:
DROP TRIGGER EMP_AFI_LOG;
-- Output: Trigger dropped.
The trigger name is all that is needed in the syntax for the DROP TRIGGER
statement. An error or the incorrect trigger may be dropped if the trigger name is not explicitly supplied, depending on the environment and schema context.