#=======================================================================
# Writer
: Lokanadham Thandlam
#=======================================================================
--Example 9–1 TRIGGER Uses
Conditional Predicates TO Detect Triggering STATEMENT
CREATE OR REPLACE TRIGGER t
BEFORE INSERT OR UPDATE OF sal, deptno OR DELETE
ON scott.emp
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;
/
--Example 9–2 INSTEAD OF
TRIGGER
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;
--INSERT INTO order_info
VALUES (999, 'Smith', 'John', 2500, TO_DATE('13-MAR-2001', 'DD-MON-YYYY'), 0)
--Example 9–3 INSTEAD OF
TRIGGER ON NESTED TABLE COLUMN OF VIEW
-- 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 INTO TABLE (SELECT d.emplist
FROM dept_view d
WHERE department_id = 10)
VALUES (1001,
'Glenn',
'AC_MGR',
10000);
SELECT employee_id,
last_name,
job_id,
salary
FROM employees
WHERE department_id = 10;
--Example 9–4 Compound
Trigger Logs Changes to One Table in Another Table
CREATE TABLE employee_salaries
(
employee_id NUMBER NOT NULL,
change_date DATE NOT NULL,
salary NUMBER (8, 2) NOT NULL,
CONSTRAINT pk_employee_salaries PRIMARY KEY (employee_id, change_date),
CONSTRAINT fk_employee_salaries FOREIGN KEY
(employee_id)
REFERENCES employees (employee_id) ON DELETE CASCADE
)
/
CREATE OR REPLACE TRIGGER maintain_employee_salaries FOR
UPDATE OF salary
ON employees
COMPOUND TRIGGER
-- Declarative Part:
-- Choose small threshhold
value to show how example works:
threshhold CONSTANT SIMPLE_INTEGER := 7;
TYPE salaries_t IS TABLE OF employee_salaries%ROWTYPE
INDEX BY SIMPLE_INTEGER;
salaries salaries_t;
idx
SIMPLE_INTEGER := 0;
PROCEDURE flush_array
IS
n CONSTANT SIMPLE_INTEGER := salaries.COUNT ();
BEGIN
FORALL j IN 1 .. n
INSERT INTO employee_salaries
VALUES salaries (j);
salaries.delete ();
idx := 0;
DBMS_OUTPUT.PUT_LINE ('Flushed ' || n || ' rows');
END flush_array;
-- AFTER EACH ROW Section:
AFTER EACH ROW
IS
BEGIN
idx := idx + 1;
salaries (idx).employee_id := :NEW.employee_id;
salaries (idx).change_date := SYSTIMESTAMP;
salaries (idx).salary := :NEW.salary;
IF idx >= threshhold
THEN
flush_array ();
END IF;
END
AFTER EACH ROW;
-- AFTER STATEMENT Section:
AFTER STATEMENT
IS
BEGIN
flush_array ();
END
AFTER STATEMENT;
END maintain_employee_salaries;
/
--Increase salary OF every
employee IN department 50 BY 10%:
UPDATE employees
SET salary = salary * 1.1
WHERE department_id = 50
/
--Wait two seconds:
BEGIN
DBMS_LOCK.SLEEP
(2);
END;
/
--Increase salary of every
employee in department 50 by 5%:
UPDATE employees
SET salary = salary * 1.05
WHERE department_id = 50
/
--See changes to employees
table reflected in employee_salaries table:
SELECT employee_id, COUNT (*) c
FROM employee_salaries
GROUP BY employee_id
/
--Example 9–5 Compound
Trigger Avoids Mutating-Table Error
CREATE OR REPLACE TRIGGER Check_Employee_Salary_Raise FOR
UPDATE OF Salary
ON Employees
COMPOUND TRIGGER
Ten_Percent CONSTANT NUMBER := 0.1;
TYPE Salaries_t IS TABLE OF Employees.Salary%TYPE;
Avg_Salaries
Salaries_t;
TYPE Department_IDs_t IS TABLE OF Employees.Department_ID%TYPE;
Department_IDs
Department_IDs_t;
-- Declare collection type
and variable:
TYPE Department_Salaries_t IS TABLE OF Employees.Salary%TYPE
INDEX BY VARCHAR2 (80);
Department_Avg_Salaries
Department_Salaries_t;
BEFORE STATEMENT
IS
BEGIN
SELECT AVG (e.Salary), NVL (e.Department_ID, -1)
BULK COLLECT INTO Avg_Salaries, Department_IDs
FROM Employees e
GROUP BY e.Department_ID;
FOR j IN 1 .. Department_IDs.COUNT ()
LOOP
Department_Avg_Salaries (Department_IDs
(j)) := Avg_Salaries (j);
END LOOP;
END
BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
IF :NEW.Salary - :Old.Salary >
Ten_Percent *
Department_Avg_Salaries (:NEW.Department_ID)
THEN
Raise_Application_Error (-20000, 'Raise too big');
END IF;
END
AFTER EACH ROW;
END Check_Employee_Salary_Raise;
--Note:
--The examples in the
following topics use these tables, which share the column Deptno:
CREATE TABLE emp
(
Empno NUMBER NOT NULL,
Ename VARCHAR2 (10),
Job VARCHAR2 (9),
Mgr NUMBER (4),
Hiredate DATE,
Sal NUMBER (7, 2),
Comm NUMBER (7, 2),
Deptno NUMBER (2) NOT NULL
);
CREATE TABLE dept
(
Deptno NUMBER (2) NOT NULL,
Dname VARCHAR2 (14),
Loc VARCHAR2 (13),
Mgr_no NUMBER,
Dept_type NUMBER
);
--Example 9–6 Foreign Key
Trigger for Child Table
CREATE OR REPLACE TRIGGER emp_dept_check
BEFORE INSERT OR UPDATE OF Deptno
ON emp
FOR EACH ROW
WHEN (NEW.Deptno IS NOT NULL)
-- Before row is inserted or
DEPTNO is updated in emp table,
-- fire this trigger to
verify that new foreign key value (DEPTNO)
-- is present in dept table.
DECLARE
Dummy INTEGER; -- Use for cursor fetch
Invalid_department EXCEPTION;
Valid_department EXCEPTION;
Mutating_table EXCEPTION;
PRAGMA EXCEPTION_INIT (Invalid_department, -4093);
PRAGMA EXCEPTION_INIT (Valid_department, -4092);
PRAGMA EXCEPTION_INIT (Mutating_table, -4091);
-- Cursor used to verify
parent key value exists.
-- If present, lock parent
key's row so it cannot be deleted
-- by another transaction
until this transaction is
-- committed or rolled back.
CURSOR Dummy_cursor (Dn NUMBER)
IS
SELECT Deptno
FROM dept
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 code 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;
/
--Example 9–7 UPDATE and
DELETE RESTRICT Trigger for Parent Table
CREATE OR REPLACE TRIGGER dept_restrict
BEFORE DELETE OR UPDATE OF Deptno
ON dept
FOR EACH ROW
-- Before row is deleted from
dept or primary key (DEPTNO) of dept is updated,
-- check for dependent
foreign key values in emp;
-- if any are found, roll
back.
DECLARE
Dummy INTEGER; -- Use for cursor fetch
employees_present EXCEPTION;
employees_not_present EXCEPTION;
PRAGMA EXCEPTION_INIT (employees_present, -4094);
PRAGMA EXCEPTION_INIT (employees_not_present, -4095);
-- Cursor used to check for
dependent foreign key values.
CURSOR Dummy_cursor (Dn NUMBER)
IS
SELECT Deptno
FROM emp
WHERE Deptno = Dn;
BEGIN
OPEN Dummy_cursor (:OLD.Deptno);
FETCH Dummy_cursor INTO Dummy;
-- If dependent foreign key
is found, raise user-specified
-- error code 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 exist
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;
--Example 9–8 UPDATE AND
DELETE SET NULL TRIGGER FOR PARENT TABLE
CREATE OR REPLACE TRIGGER dept_set_null
AFTER DELETE OR UPDATE OF Deptno
ON dept
FOR EACH ROW
-- Before row is deleted from
dept or primary key (DEPTNO) of dept is updated,
-- set all corresponding
dependent foreign key values in emp to NULL:
BEGIN
IF UPDATING AND :OLD.Deptno != :NEW.Deptno OR DELETING
THEN
UPDATE emp
SET emp.Deptno = NULL
WHERE emp.Deptno = :OLD.Deptno;
END IF;
END;
/
--Example 9–9 DELETE CASCADE
Trigger for Parent Table
CREATE OR REPLACE TRIGGER dept_del_cascade
AFTER DELETE
ON dept
FOR EACH ROW
-- Before row is deleted from
dept,
-- delete all rows from emp
table whose DEPTNO is same as
-- DEPTNO being deleted from
dept table:
BEGIN
DELETE FROM emp
WHERE emp.Deptno = :OLD.Deptno;
END;
/
--Example 9–10 UPDATE CASCADE
Trigger for Parent Table
-- Generate sequence number
to be used as flag
-- for determining if update
occurred on column:
CREATE SEQUENCE Update_sequence
INCREMENT BY 1
MAXVALUE 5000
CYCLE;
CREATE OR REPLACE PACKAGE Integritypackage
AUTHID DEFINER
AS
Updateseq NUMBER;
END Integritypackage;
/
CREATE OR REPLACE PACKAGE BODY Integritypackage
AS
END Integritypackage;
/
-- Create flag col:
ALTER TABLE emp ADD Update_id NUMBER;
CREATE OR REPLACE TRIGGER dept_cascade1
BEFORE UPDATE OF Deptno
ON dept
DECLARE
-- Before updating dept table
(this is a statement trigger),
-- generate sequence number
-- & assign it to public
variable UPDATESEQ of
-- user-defined package named
INTEGRITYPACKAGE:
BEGIN
Integritypackage.Updateseq := Update_sequence.NEXTVAL;
END;
/
CREATE OR REPLACE TRIGGER dept_cascade2
AFTER DELETE OR UPDATE OF Deptno
ON dept
FOR EACH ROW
-- For each department number
in dept that is updated,
-- cascade update to
dependent foreign keys in emp table.
-- Cascade update only if
child row was not updated by this trigger:
BEGIN
IF UPDATING
THEN
UPDATE emp
SET Deptno = :NEW.Deptno, Update_id = Integritypackage.Updateseq --from 1st
WHERE emp.Deptno = :OLD.Deptno AND Update_id IS NULL;
/* Only NULL if not updated
by 3rd trigger
fired by same triggering statement */
END IF;
IF DELETING
THEN
-- After row
is deleted from dept,
-- delete
all rows from emp table whose DEPTNO is same as
-- DEPTNO
being deleted from dept table:
DELETE FROM emp
WHERE emp.Deptno = :OLD.Deptno;
END IF;
END;
/
CREATE OR REPLACE TRIGGER dept_cascade3
AFTER UPDATE OF Deptno
ON dept
BEGIN
UPDATE emp
SET Update_id = NULL
WHERE Update_id = Integritypackage.Updateseq;
END;
/
--Note:
--Example 9–11 needs
this data structure:
CREATE TABLE Salgrade
(
Grade NUMBER,
Losal NUMBER,
Hisal NUMBER,
Job_classification VARCHAR2 (9)
);
--Example 9–11 Trigger Checks
Complex Constraints
CREATE OR REPLACE TRIGGER salary_check
BEFORE INSERT OR UPDATE OF Sal, Job
ON Emp
FOR EACH ROW
DECLARE
Minsal NUMBER;
Maxsal NUMBER;
Salary_out_of_range EXCEPTION;
PRAGMA EXCEPTION_INIT (Salary_out_of_range, -4096);
BEGIN
/* Retrieve minimum &
maximum salary for employee's new job classification
from SALGRADE table into MINSAL and MAXSAL:
*/
SELECT Losal, Hisal
INTO Minsal, Maxsal
FROM Salgrade
WHERE Job_classification = :NEW.Job;
/* If employee's new salary
is less than or greater than
job classification's limits, raise
exception.
Exception message is returned and 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');
END;
/
--Example 9–12 TRIGGER
Enforces Security
CREATE OR REPLACE TRIGGER Emp_permit_changes
BEFORE INSERT OR DELETE OR UPDATE
ON Emp
DECLARE
Dummy INTEGER;
Not_on_weekends EXCEPTION;
Not_on_holidays EXCEPTION;
Non_working_hours EXCEPTION;
PRAGMA EXCEPTION_INIT (Not_on_weekends, -4097);
PRAGMA EXCEPTION_INIT (Not_on_holidays, -4098);
PRAGMA EXCEPTION_INIT (Non_working_hours, -4099);
BEGIN
-- Check for weekends:
IF (TO_CHAR (SYSDATE, 'DAY') = 'SAT' OR TO_CHAR (SYSDATE, 'DAY') = 'SUN')
THEN
RAISE Not_on_weekends;
END IF;
-- Check for company
holidays:
SELECT COUNT (*)
INTO Dummy
FROM Company_holidays
WHERE Day = SYSDATE;
IF dummy > 0
THEN
RAISE Not_on_holidays;
END IF;
-- Check for work hours (8am
to 6pm):
IF (TO_CHAR (SYSDATE, 'HH24') < 8 OR TO_CHAR (SYSDATE, 'HH24') > 18)
THEN
RAISE Non_working_hours;
END IF;
EXCEPTION
WHEN Not_on_weekends
THEN
Raise_application_error (
-20324,
'Might not
change ' || 'employee table during the
weekend');
WHEN Not_on_holidays
THEN
Raise_application_error (
-20325,
'Might not
change ' || 'employee table during a
holiday');
WHEN Non_working_hours
THEN
Raise_application_error (
-20326,
'Might not
change ' || 'emp table during nonworking
hours');
END;
/
--Note:
--Example 9–13 needs
this change to this data structure:
ALTER TABLE Emp ADD(
Uppername VARCHAR2(20),
Soundexname VARCHAR2(20));
--Example 9–13 Trigger
Derives New Column Values
CREATE OR REPLACE TRIGGER Derived
BEFORE INSERT OR UPDATE OF Ename
ON Emp
/* Before updating the ENAME
field, derive the values for
the UPPERNAME and SOUNDEXNAME fields.
Restrict users
from updating these fields directly: */
FOR EACH ROW
BEGIN
:NEW.Uppername := UPPER (:NEW.Ename);
:NEW.Soundexname := SOUNDEX (:NEW.Ename);
END;
/
CREATE OR REPLACE TYPE Book_t AS OBJECT
(Booknum NUMBER,
Title VARCHAR2 (20),
Author VARCHAR2 (20),
Available CHAR (1));
/
CREATE OR REPLACE TYPE Book_list_t AS TABLE OF Book_t;
/
--THE TABLE Book_table IS
created AND populated LIKE this:
DROP TABLE Book_table;
CREATE TABLE Book_table
(
Booknum NUMBER,
Section VARCHAR2 (20),
Title VARCHAR2 (20),
Author VARCHAR2 (20),
Available CHAR (1)
);
INSERT INTO Book_table (Booknum,
Section,
Title,
Author,
Available)
VALUES (121001,
'Classic',
'Iliad',
'Homer',
'Y');
INSERT INTO Book_table (Booknum,
Section,
Title,
Author,
Available)
VALUES (121002,
'Novel',
'Gone with the Wind',
'Mitchell M',
'N');
--SELECT * FROM Book_table
ORDER BY Booknum;
DROP TABLE Library_table;
CREATE TABLE
Library_table (Section VARCHAR2 (20));
INSERT INTO
Library_table (Section)
VALUES ('Novel');
INSERT INTO
Library_table (Section)
VALUES ('Classic');
SELECT *
FROM Library_table
ORDER BY Section;
CREATE OR REPLACE VIEW Library_view
AS
SELECT i.Section,
CAST (MULTISET (SELECT b.Booknum,
b.Title,
b.Author,
b.Available
FROM Book_table b
WHERE b.Section = i.Section) AS Book_list_t)
BOOKLIST
FROM Library_table i;
CREATE OR REPLACE TRIGGER Library_trigger
INSTEAD OF INSERT
ON Library_view
FOR EACH ROW
DECLARE
Bookvar Book_t;
I INTEGER;
BEGIN
INSERT INTO Library_table
VALUES (:NEW.Section);
FOR i IN 1 .. :NEW.Booklist.COUNT
LOOP
Bookvar := :NEW.Booklist (i);
INSERT INTO Book_table (Booknum,
Section,
Title,
Author,
Available)
VALUES (Bookvar.booknum,
:NEW.Section,
Bookvar.Title,
Bookvar.Author,
bookvar.Available);
END LOOP;
END;
/
--Insert a new row into
Library_view:
INSERT INTO
Library_view (Section, Booklist)
VALUES ('History',
book_list_t (book_t (121330,
'Alexander',
'Mirth',
'Y')));
--See the effect on
Library_view:
SELECT *
FROM Library_view
ORDER BY Section;
--Example 9–14 TRIGGER Logs
Changes TO EMPLOYEES.SALARY
--CREATE log table:
DROP TABLE Emp_log;
CREATE TABLE Emp_log
(
Emp_id NUMBER,
Log_date DATE,
New_salary NUMBER,
Action VARCHAR2 (20)
);
--CREATE TRIGGER that inserts
row in log table after EMPLOYEES.SALARY is updated:
CREATE OR REPLACE TRIGGER log_salary_increase
AFTER UPDATE OF salary
ON employees
FOR EACH ROW
BEGIN
INSERT INTO Emp_log (Emp_id,
Log_date,
New_salary,
Action)
VALUES (:NEW.employee_id,
SYSDATE,
:NEW.salary,
'New Salary');
END;
/
UPDATE EMPLOYEES.SALARY:
UPDATE employees
SET salary = salary + 1000.0
WHERE Department_id = 20;
SELECT * FROM Emp_log;
--Example 9–15 Conditional
TRIGGER Prints Salary CHANGE Information
CREATE OR REPLACE TRIGGER print_salary_changes
BEFORE DELETE OR INSERT OR UPDATE
ON employees
FOR EACH ROW
WHEN (NEW.job_id <> 'AD_PRES') -- do not print information about President
DECLARE
sal_diff NUMBER;
BEGIN
sal_diff := :NEW.salary - :OLD.salary;
DBMS_OUTPUT.PUT
(:NEW.last_name || ': ');
DBMS_OUTPUT.PUT
('Old salary = ' || :OLD.salary || ', ');
DBMS_OUTPUT.PUT
('New salary = ' || :NEW.salary || ', ');
DBMS_OUTPUT.PUT_LINE
('Difference: ' || sal_diff);
END;
/
SELECT last_name,
department_id,
salary,
job_id
FROM employees
WHERE department_id IN (10, 20, 90)
ORDER BY
department_id, last_name;
--Triggering statement:
UPDATE employees
SET salary = salary * 1.05
WHERE department_id IN (10, 20, 90);
--QUERY:
SELECT salary
FROM employees
WHERE job_id = 'AD_PRES';
--Example 9–16 TRIGGER
Modifies CLOB COLUMNS
DROP TABLE tab1;
CREATE TABLE tab1 (c1 CLOB);
INSERT INTO tab1
VALUES ('<h1>HTML Document
Fragment</h1><p>Some text.', 3);
CREATE OR REPLACE TRIGGER trg1
BEFORE UPDATE
ON tab1
FOR EACH ROW
BEGIN
DBMS_OUTPUT.PUT_LINE
('Old value of CLOB column: ' || :OLD.c1);
DBMS_OUTPUT.PUT_LINE
('Proposed new value of CLOB
column: ' || :NEW.c1);
:NEW.c1 := :NEW.c1 || TO_CLOB ('<hr><p>Standard
footer paragraph.');
DBMS_OUTPUT.PUT_LINE
('Final value of CLOB column: ' || :NEW.c1);
END;
/
SET SERVEROUTPUT ON;
UPDATE tab1
SET c1 = '<h1>Different Document
Fragment</h1><p>Different text.';
SELECT * FROM tab1;
--Example 9–17 TRIGGER WITH
REFERENCING Clause
CREATE TABLE new
(
field1 NUMBER,
field2 VARCHAR2 (20)
);
CREATE OR REPLACE TRIGGER Print_salary_changes
BEFORE UPDATE
ON new
REFERENCING NEW AS Newest
FOR EACH ROW
BEGIN
:Newest.Field2 := TO_CHAR (:newest.field1);
END;
/
--Example 9–18 TRIGGER
REFERENCES OBJECT_VALUE Pseudocolumn
--CREATE , populate, and show
object table:
CREATE OR REPLACE TYPE t
AUTHID DEFINER AS OBJECT
(n NUMBER, m NUMBER)
/
CREATE TABLE tbl OF t
/
BEGIN
FOR j IN 1 .. 5
LOOP
INSERT INTO tbl
VALUES (t (j, 0));
END LOOP;
END;
/
SELECT *
FROM tbl
ORDER BY n;
CREATE TABLE tbl_history
(
d DATE,
old_obj t,
new_obj t
)
/
CREATE OR REPLACE TRIGGER Tbl_Trg
AFTER UPDATE
ON tbl
FOR EACH ROW
BEGIN
INSERT INTO tbl_history (d, old_obj, new_obj)
VALUES (SYSDATE, :OLD.OBJECT_VALUE, :NEW.OBJECT_VALUE);
END Tbl_Trg;
/
--Update object table:
UPDATE tbl
SET tbl.n = tbl.n + 1
/
--Show old and new values:
BEGIN
FOR j IN (SELECT d, old_obj, new_obj FROM tbl_history)
LOOP
DBMS_OUTPUT.PUT_LINE (
j.d
|| ' -- old: '
|| j.old_obj.n
|| ' '
|| j.old_obj.m
|| ' -- new: '
|| j.new_obj.n
|| ' '
|| j.new_obj.m);
END LOOP;
END;
/
--SCHEMA Triggers
--Example 9–19 BEFORE
STATEMENT TRIGGER ON SAMPLE SCHEMA HR
CREATE OR REPLACE TRIGGER drop_trigger
BEFORE DROP
ON hr.SCHEMA
BEGIN
RAISE_APPLICATION_ERROR (num => -20000, msg => 'Cannot drop object');
END;
/
--DATABASE Triggers
--Example 9–20 AFTER
STATEMENT TRIGGER ON DATABASE
CREATE TRIGGER log_errors
AFTER SERVERERROR
ON DATABASE
BEGIN
IF (IS_SERVERERROR (1017))
THEN
NULL; -- (substitute code that
processes logon error)
ELSE
NULL; -- (substitute code that logs
error code)
END IF;
END;
/
--Example 9–21 TRIGGER
Monitors Logons
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
--Example 9–22 INSTEAD OF
CREATE TRIGGER ON SCHEMA
CREATE OR REPLACE TRIGGER t
INSTEAD OF CREATE
ON SCHEMA
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE T (n NUMBER, m
NUMBER)';
END;
/
--Example 9–23 Trigger
Invokes Java Subprogram
CREATE OR REPLACE PROCEDURE Before_delete (Id IN NUMBER, Ename VARCHAR2)
IS
LANGUAGE JAVA
NAME 'thjvTriggers.beforeDelete
(oracle.sql.NUMBER, oracle.sql.CHAR)';
CREATE OR REPLACE TRIGGER Pre_del_trigger
BEFORE DELETE
ON Tab
FOR EACH ROW
CALL Before_delete (:OLD.Id, :OLD.Ename)
/
--Example 9–24 TRIGGER Cannot
Handle EXCEPTION IF Remote DATABASE IS Unavailable
CREATE OR REPLACE TRIGGER employees_tr
AFTER INSERT
ON employees
FOR EACH ROW
BEGIN
-- When remote database is
unavailable, compilation fails here:
INSERT INTO employees@remote (employee_id,
first_name,
last_name,
email,
hire_date,
job_id)
VALUES (99,
'Jane',
'Doe',
'jane.doe@example.com',
SYSDATE,
'ST_MAN');
EXCEPTION
WHEN OTHERS
THEN
INSERT INTO emp_log (Emp_id,
Log_date,
New_salary,
Action)
VALUES (99,
SYSDATE,
NULL,
'Could not insert');
RAISE;
END;
/
--Example 9–25 Workaround FOR
Example 9–24
CREATE OR REPLACE PROCEDURE insert_row_proc
AUTHID CURRENT_USER
AS
no_remote_db EXCEPTION; -- declare exception
PRAGMA EXCEPTION_INIT (no_remote_db, -20000);
BEGIN
INSERT INTO employees@remote (employee_id,
first_name,
last_name,
email,
hire_date,
job_id)
VALUES (99,
'Jane',
'Doe',
'jane.doe@example.com',
SYSDATE,
'ST_MAN');
EXCEPTION
WHEN OTHERS
THEN
INSERT INTO emp_log (Emp_id,
Log_date,
New_salary,
Action)
VALUES (99,
SYSDATE,
NULL,
'Could not insert row.');
RAISE_APPLICATION_ERROR (-20000, 'Remote database is
unavailable.');
END;
/
CREATE OR REPLACE TRIGGER employees_tr
AFTER INSERT
ON employees
FOR EACH ROW
BEGIN
insert_row_proc;
END;
/
--Example 9–26 TRIGGER Causes
Mutating-TABLE ERROR
-- Create log table
DROP TABLE LOG;
CREATE TABLE LOG
(
emp_id NUMBER (6),
l_name VARCHAR2 (25),
f_name VARCHAR2 (20)
);
-- Create trigger that
updates log and then reads employees
CREATE OR REPLACE TRIGGER log_deletions
AFTER DELETE
ON employees
FOR EACH ROW
DECLARE
n INTEGER;
BEGIN
INSERT INTO LOG
VALUES (:OLD.employee_id, :OLD.last_name, :OLD.first_name);
SELECT COUNT (*) INTO n FROM employees;
DBMS_OUTPUT.PUT_LINE
('There are now ' || n || ' employees.');
END;
/
-- Issue triggering
statement:
DELETE FROM employees
WHERE employee_id = 197;
DELETE FROM employees
WHERE employee_id = 197;
--Example 9–27 Update Cascade
DROP TABLE p;
CREATE TABLE p (p1 NUMBER CONSTRAINT pk_p_p1 PRIMARY KEY);
INSERT INTO p
VALUES (1);
INSERT INTO p
VALUES (2);
INSERT INTO p
VALUES (3);
DROP TABLE f;
CREATE TABLE f (f1 NUMBER CONSTRAINT fk_f_f1 REFERENCES p);
INSERT INTO f
VALUES (1);
INSERT INTO f
VALUES (2);
INSERT INTO f
VALUES (3);
CREATE TRIGGER pt
AFTER UPDATE
ON p
FOR EACH ROW
BEGIN
UPDATE f
SET f1 = :NEW.p1
WHERE f1 = :OLD.p1;
END;
/
--QUERY:
SELECT *
FROM p
ORDER BY p1;
--QUERY:
SELECT *
FROM f
ORDER BY f1;
--Issue triggering STATEMENT:
UPDATE p
SET p1 = p1 + 1;
--QUERY:
SELECT *
FROM p
ORDER BY p1;
--QUERY:
SELECT *
FROM f
ORDER BY f1;
--Example 9–28 Viewing
Information About TRIGGERS
CREATE OR REPLACE TRIGGER Emp_count
AFTER DELETE
ON employees
DECLARE
n INTEGER;
BEGIN
SELECT COUNT (*) INTO n FROM employees;
DBMS_OUTPUT.PUT_LINE
('There are now ' || n || ' employees.');
END;
/
COLUMN Trigger_type FORMAT A15
COLUMN Triggering_event FORMAT A16
COLUMN Table_name FORMAT A11
COLUMN Trigger_body FORMAT A50
--QUERY:
SELECT Trigger_type, Triggering_event, Table_name
FROM USER_TRIGGERS
WHERE Trigger_name = 'EMP_COUNT';
--QUERY:
SELECT Trigger_body
FROM USER_TRIGGERS
WHERE Trigger_name = 'EMP_COUNT';