What are Triggers? Triggers are special types of stored procedures in PL/SQL that are automatically executed in response to certain events or changes in a database. These events could include data manipulation language (DML) statements like INSERT, UPDATE, and DELETE or data definition language (DDL) statements like CREATE, ALTER, and DROP. Trigger Syntax The syntax for creating a trigger in PL/SQL is as follows: CREATE [OR REPLACE ] TRIGGER trigger_name {BEFORE | AFTER | INSTEAD OF } {INSERT [OR] | UPDATE [OR] | DELETE} [OF col_name] ON table_name [REFERENCING OLD AS o NEW AS n] [FOR EACH ROW] WHEN (condition) DECLARE Declaration-statements BEGIN Executable-statements EXCEPTION Exception-handling-statements END Trigger Execution Time Triggers can be executed either before or after the triggering event occurs. A BEFORE trigger is executed before the triggering event, while an AFTER trigger is executed after the triggering event. An INSTEAD OF trigger is executed instead of the triggering event. Trigger Event The trigger event is the event that causes the trigger to execute. In PL/SQL, triggers can be defined on INSERT, UPDATE, and DELETE events. These events are defined using the BEFORE or AFTER keyword in the trigger definition. Trigger Table The trigger table is the table that the trigger is defined on. This is specified using the ON keyword in the trigger definition. OLD and NEW Pseudorecords The OLD and NEW pseudorecords are used in the trigger code to access the old and new values of the data being modified. The OLD pseudorecord contains the old values of the row being modified, while the NEW pseudorecord contains the new values. FOR EACH ROW The FOR EACH ROW clause in the trigger definition specifies that the trigger is executed once for each row affected by the triggering event. SELECT trigger_name, table_name, trigger_type FROM user_triggers; ALTER TRIGGER TRIGGER_NAME DISABLE; ALTER TRIGGER TRIGGER_NAME ENABLE; BEFORE INSERT Trigger Example: A BEFORE INSERT trigger is executed before a new row is inserted into the table. Here is an example of a BEFORE INSERT trigger that validates the salary of a new employee: CREATE OR REPLACE TRIGGER validate_salary BEFORE INSERT ON oehr_employees FOR EACH ROW BEGIN IF :NEW.salary < 5000 THEN RAISE_APPLICATION_ERROR(-20001, 'Salary too low'); END IF; END; INSERT INTO OEHR_EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE_NUMBER,HIRE_DATE,JOB_ID, SALARY,COMMISSION_PCT,MANAGER_ID,DEPARTMENT_ID) VALUES (669, 'Alexander','Hunold','AAHUNOLD’','590.423.4567','02/07/2012','IT_PROG',4990,NULL,102,50) BEFORE UPDATE Trigger Example: A BEFORE UPDATE trigger is executed before an existing row is updated. Here is an example of a BEFORE UPDATE trigger that prevents updates to the hire_date column: CREATE OR REPLACE TRIGGER prevent_hire_date_update BEFORE UPDATE ON oehr_employees FOR EACH ROW BEGIN IF UPDATING('hire_date') THEN RAISE_APPLICATION_ERROR(-20001, 'Cannot update hire date'); END IF; END; UPDATE oehr_employees SET HIRE_DATE = '10/25/2012' WHERE EMPLOYEE_ID = 668; BEFORE DELETE Trigger Example: BEFORE DELETE trigger is executed before an existing row is deleted. Here is an example of a BEFORE DELETE trigger that prevents the deletion of employees with a higher salary: CREATE OR REPLACE TRIGGER prevent_high_salary_deletion BEFORE DELETE ON oehr_employees FOR EACH ROW BEGIN IF :OLD.salary > 20000 THEN RAISE_APPLICATION_ERROR(-20001, 'Cannot delete employees with high salary'); END IF; END; DELETE FROM oehr_employees WHERE EMPLOYEE_ID = 100; AFTER INSERT Trigger Example: An AFTER INSERT trigger is executed after a new row is inserted into the table. Here is an example of an AFTER INSERT trigger that updates the department manager of a newly added employee: CREATE OR REPLACE TRIGGER update_department_manager AFTER INSERT ON oehr_employees FOR EACH ROW BEGIN UPDATE oehr_departments SET manager_id = :NEW.employee_id WHERE department_id = :NEW.department_id; END; INSERT INTO OEHR_EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE_NUMBER,HIRE_DATE,JOB_ID, SALARY,COMMISSION_PCT,MANAGER_ID,DEPARTMENT_ID) VALUES (777, 'Test','Test','Tessssst','590.423.4567','02/07/2012','IT_PROG',6000,NULL,102,50) AFTER UPDATE Trigger Example: An AFTER UPDATE trigger is executed after an existing row is updated. Here is an example of an AFTER UPDATE trigger that inserts a row into an audit table for each update: CREATE TABLE audit_table ( audit_id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY, audit_date TIMESTAMP DEFAULT SYSTIMESTAMP, message VARCHAR2(200) ); AFTER UPDATE ON oehr_employees FOR EACH ROW BEGIN INSERT INTO audit_table(audit_date, message) VALUES (SYSTIMESTAMP, 'Employee updated: ' || :OLD.first_name || ', ' || :OLD.employee_id); END; Update oehr_employees set first_name = 'Test1' where first_name = 'Test' AFTER DELETE Trigger Example: An AFTER DELETE trigger is executed after an existing row is deleted. Here is an example of an AFTER DELETE trigger that updates the department manager to NULL when an employee is deleted: CREATE OR REPLACE TRIGGER update_manager_on_delete AFTER DELETE ON oehr_employees FOR EACH ROW BEGIN UPDATE oehr_departments SET manager_id = NULL WHERE manager_id = :OLD.employee_id; END; DELETE FROM oehr_employees WHERE EMPLOYEE_ID = 500; "Before insert" trigger for the employees table that sets a default value for the salary column: CREATE OR REPLACE TRIGGER trg_set_default_salary BEFORE INSERT ON oehr_employees FOR EACH ROW DECLARE BEGIN IF :new.salary IS NULL THEN :new.salary := 5000; END IF; END; INSERT INTO oehr_employees (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE_NUMBER,HIRE_DATE,JOB_ID, SALARY,COMMISSION_PCT,MANAGER_ID,DEPARTMENT_ID) VALUES (668, 'John', 'Doe', 'SSSKING', '515.123.4568', '10/26/2011','AD_VP', NULL, NULL, NULL, 90); "Before insert" trigger for the jobs table that sets a default value for the max_salary column: CREATE OR REPLACE TRIGGER trg_set_default_max_salary BEFORE INSERT ON oehr_jobs FOR EACH ROW DECLARE BEGIN IF :new.max_salary IS NULL THEN :new.max_salary := 20000; END IF; END; INSERT INTO oehr_jobs (job_id, job_title, min_salary) VALUES ('DEV', 'Developer', 5000); "Before update" trigger for the jobs table that prevents updates to the min_salary column that would result in a value less than 5000: CREATE OR REPLACE TRIGGER trg_prevent_min_salary_update BEFORE UPDATE OF min_salary ON oehr_jobs FOR EACH ROW DECLARE BEGIN IF :new.min_salary < 3000 THEN RAISE_APPLICATION_ERROR(-20001, 'Minimum salary cannot be less than 3000.'); END IF; END; UPDATE oehr_jobs SET min_salary = 2500 WHERE job_id = 'AD_ASST'; "After update" trigger for the departments table that logs changes made to the location_id column: CREATE TABLE department_logs ( department_id NUMBER(10) NOT NULL, old_location_id NUMBER(10) NOT NULL, new_location_id NUMBER(10) NOT NULL, changed_by VARCHAR2(50) NOT NULL ); CREATE OR REPLACE TRIGGER trg_log_department_changes AFTER UPDATE OF location_id ON oehr_departments FOR EACH ROW DECLARE BEGIN INSERT INTO department_logs (department_id, old_location_id, new_location_id, changed_by) VALUES (:old.department_id, :old.location_id, :new.location_id, USER); END; UPDATE oehr_departments SET location_id = 1700 WHERE department_id = 20; Example before delete: jobs table CREATE OR REPLACE TRIGGER prevent_job_delete BEFORE DELETE ON oehr_jobs FOR EACH ROW DECLARE employees_with_job INTEGER; BEGIN SELECT COUNT(*) INTO employees_with_job FROM oehr_employees WHERE job_id = :OLD.job_id; IF employees_with_job > 0 THEN RAISE_APPLICATION_ERROR(-20001, 'This job cannot be deleted because it is currently held by one or more employees.'); END IF; END; / DELETE FROM oehr_jobs WHERE job_id = 'IT_PROG'; Example 1: employees table Suppose you want to create a before update trigger for the employees table that prevents any employee from being given a salary increase greater than 10% of their current salary. Here's how you can do it: CREATE OR REPLACE TRIGGER prevent_salary_increase BEFORE UPDATE OF salary ON oehr_employees FOR EACH ROW BEGIN IF :NEW.salary > (:OLD.salary * 1.1) THEN RAISE_APPLICATION_ERROR(-20001, 'A salary increase of more than 10% is not allowed.'); END IF; END; / UPDATE oehr_employees SET salary = salary * 1.15 WHERE employee_id = 668; Example 2: orders table Suppose you want to create a before update trigger for the orders table that prevents any order from being given a status of "shipped" if it is not already in "processing" status. Here's how you can do it: CREATE OR REPLACE TRIGGER prevent_status_change BEFORE UPDATE OF ORDER_STATUS ON oehr_orders FOR EACH ROW BEGIN IF :NEW.ORDER_STATUS = 2 AND :OLD.ORDER_STATUS != 1 THEN RAISE_APPLICATION_ERROR(-20001, 'An order cannot be marked as 2 if it is not in 1 status.'); END IF; END; BEFORE INSERT Example 1: employees table Suppose you want to create a before insert trigger for the employees table that sets the hire date to the current date if no hire date is provided. Here's how you can do it: CREATE OR REPLACE TRIGGER set_hire_date BEFORE INSERT ON employees FOR EACH ROW BEGIN IF :NEW.hire_date IS NULL THEN :NEW.hire_date := SYSDATE; END IF; END; / INSERT INTO employees (employee_id, first_name, last_name, email, job_id) VALUES (99999, 'John', 'Doe', 'jdoe@example.com', 'IT_PROG'); Example 2: orders table Suppose you want to create a before insert trigger for the orders table that sets the order date to the current date if no order date is provided. Here's how you can do it: CREATE OR REPLACE TRIGGER set_order_date BEFORE INSERT ON orders FOR EACH ROW BEGIN IF :NEW.order_date IS NULL THEN :NEW.order_date := SYSDATE; END IF; END; / INSERT INTO orders (customer_id, order_total) VALUES (12345, 100.00); AFTER INSERT Example 1: employees table Suppose you want to create an after insert trigger for the employees table that adds a record to the jobs table whenever a new job is added to the employees table. Here's how you can do it: CREATE OR REPLACE TRIGGER add_job_to_jobs_table CREATE OR REPLACE TRIGGER add_job_to_jobs_table AFTER INSERT ON oehr_employees FOR EACH ROW BEGIN INSERT INTO oehr_jobs (job_id, min_salary, max_salary) VALUES (:NEW.job_id, :NEW.salary, :NEW.salary); END; INSERT INTO employees (employee_id, first_name, last_name, email, phone_number, hire_date, job_id, salary, commission_pct, manager_id, department_id) VALUES (1001, 'John', 'Doe', 'jdoe@example.com', '555-1234', SYSDATE, 'ANALYST', 5000, NULL, 100, 50); SELECT * FROM oehr_jobs WHERE min_salary = 5000 BEFORE DELETE Example 3: jobs table CREATE OR REPLACE TRIGGER prevent_job_delete BEFORE DELETE ON oehr_jobs FOR EACH ROW DECLARE employees_with_job INTEGER; BEGIN SELECT COUNT(*) INTO employees_with_job FROM oehr_employees WHERE job_id = :OLD.job_id; IF employees_with_job > 0 THEN RAISE_APPLICATION_ERROR(-20001, 'This job cannot be deleted because it is currently held by one or more employees.'); END IF; END; / DELETE FROM oehr_jobs WHERE job_id = 'IT_PROG'; BEFORE UPDATE Example 1: employees table Suppose you want to create a before update trigger for the employees table that prevents any employee from being given a salary increase greater than 10% of their current salary. Here's how you can do it: CREATE OR REPLACE TRIGGER prevent_salary_increase BEFORE UPDATE OF salary ON oehr_employees FOR EACH ROW BEGIN IF :NEW.salary > (:OLD.salary * 1.1) THEN RAISE_APPLICATION_ERROR(-20001, 'A salary increase of more than 10% is not allowed.'); END IF; END; / UPDATE oehr_employees SET salary = salary * 1.15 WHERE employee_id = 668; Example 2: orders table Suppose you want to create a before update trigger for the orders table that prevents any order from being given a status of "shipped" if it is not already in "processing" status. Here's how you can do it: CREATE OR REPLACE TRIGGER prevent_status_change BEFORE UPDATE OF ORDER_STATUS ON oehr_orders FOR EACH ROW BEGIN IF :NEW.ORDER_STATUS = 2 AND :OLD.ORDER_STATUS != 1 THEN RAISE_APPLICATION_ERROR(-20001, 'An order cannot be marked as 2 if it is not in 1 status.'); END IF; END; / UPDATE oehr_orders SET order_status = 2 WHERE order_id = 2458; BEFORE INSERT Example 1: employees table Suppose you want to create a before insert trigger for the employees table that sets the hire date to the current date if no hire date is provided. Here's how you can do it: CREATE OR REPLACE TRIGGER set_hire_date BEFORE INSERT ON employees FOR EACH ROW BEGIN IF :NEW.hire_date IS NULL THEN :NEW.hire_date := SYSDATE; END IF; END; / INSERT INTO employees (employee_id, first_name, last_name, email, job_id) VALUES (99999, 'John', 'Doe', 'jdoe@example.com', 'IT_PROG'); Example 2: orders table Suppose you want to create a before insert trigger for the orders table that sets the order date to the current date if no order date is provided. Here's how you can do it: CREATE OR REPLACE TRIGGER set_order_date BEFORE INSERT ON orders FOR EACH ROW BEGIN IF :NEW.order_date IS NULL THEN :NEW.order_date := SYSDATE; END IF; END; / INSERT INTO orders (customer_id, order_total) VALUES (12345, 100.00); AFTER DELETE Example 1: customers table Suppose you want to create an after delete trigger for the customers table that removes any orders associated with the deleted customer from the orders table. Here's how you can do it: CREATE OR REPLACE TRIGGER remove_orders_for_customer AFTER DELETE ON customers FOR EACH ROW BEGIN DELETE FROM orders WHERE customer_id = :OLD.customer_id; END; / DELETE FROM customers WHERE customer_id = 12345; SELECT * FROM orders WHERE customer_id = 12345; -- should return no rows Example 2: orders table Suppose you want to create an after delete trigger for the orders table that updates the order count for the department associated with the deleted order in the departments table. Here's how you can do it: CREATE OR REPLACE TRIGGER update_order_count AFTER DELETE ON orders FOR EACH ROW BEGIN UPDATE departments SET order_count = order_count - 1 WHERE department_id = :OLD.department_id; END; / DELETE FROM orders WHERE order_id = 1000; SELECT * FROM departments WHERE department_id = 50; -- should show a decrease in order_count AFTER UPDATE Example 1: employees table Suppose you want to create an after update trigger for the employees table that updates the salary of any employee who has been promoted. Here's how you can do it: CREATE OR REPLACE TRIGGER update_salary_on_promotion AFTER UPDATE ON employees FOR EACH ROW WHEN (NEW.job_id <> OLD.job_id AND NEW.salary > OLD.salary) BEGIN UPDATE employees SET salary = :NEW.salary WHERE employee_id = :OLD.employee_id; END; / UPDATE employees SET job_id = 'MANAGER' WHERE employee_id = 100; SELECT salary FROM employees WHERE employee_id = 100; -- should show an increase in salary Example 2: orders table Suppose you want to create an after update trigger for the orders table that sends an email to the customer whenever their order status changes. Here's how you can do it: CREATE OR REPLACE TRIGGER send_email_on_order_status_change AFTER UPDATE OF order_status ON oehr_orders FOR EACH ROW BEGIN IF :NEW.customer_id IS NOT NULL THEN dbms_output.put_line('Email sent to customer with ID ' || :NEW.customer_id); END IF; END; / UPDATE orders SET order_status = 'SHIPPED' WHERE order_id = 1000; AFTER INSERT Example 1: employees table Suppose you want to create an after insert trigger for the employees table that adds a record to the jobs table whenever a new job is added to the employees table. Here's how you can do it: CREATE OR REPLACE TRIGGER add_job_to_jobs_table CREATE OR REPLACE TRIGGER add_job_to_jobs_table AFTER INSERT ON oehr_employees FOR EACH ROW BEGIN INSERT INTO oehr_jobs (job_id, min_salary, max_salary) VALUES (:NEW.job_id, :NEW.salary, :NEW.salary); END; INSERT INTO employees (employee_id, first_name, last_name, email, phone_number, hire_date, job_id, salary, commission_pct, manager_id, department_id) VALUES (1001, 'John', 'Doe', 'jdoe@example.com', '555-1234', SYSDATE, 'ANALYST', 5000, NULL, 100, 50); SELECT * FROM oehr_jobs WHERE min_salary = 5000