Theory, Practice & Methodology of Relational Database Design and Programming Copyright © Ellis Cohen 2002-2008 Row Triggers These slides are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License. For more information on how you may use them, please see http://www.openlineconsult.com/db 1 Overview of Lecture Row Triggers Using Row Triggers for Enforcing State Constraints BEFORE Row Triggers Collecting Modifications for AFTER Statement Triggers Querying Triggered Tables with Row Triggers Modifying Triggered Tables with Row Triggers Cascading Row Triggers INSTEAD OF Triggers Implementing Materialized Views © Ellis Cohen 2001-2008 2 Row Triggers © Ellis Cohen 2001-2008 3 Reject Excessive Salary Increases Suppose we want to reject excessive salary increases We want to make sure that no modification of an employee's salary results in increasing the salary by more than 10% Table-Based Constraint Context: UPDATE OF sal ON Emps How would we use statement triggers to implement rejection-based enforcement? © Ellis Cohen 2001-2008 4 Two-Phase Implementation 1. First we would have to use a BEFORE trigger to remember the salaries of each employee before the UPDATE (in a temporary table) -- each connection has its own copy, and it is -- emptied at the end of each transaction CREATE GLOBAL TEMPORARY TABLE EmpSals( empno int, sal number ) 2. Then we'd have to use an AFTER trigger to check those salaries. © Ellis Cohen 2001-2008 5 Statement Trigger Implementation CREATE OR REPLACE TRIGGER save_sals BEFORE UPDATE OF sal ON Emps Temporary BEGIN Table INSERT INTO EmpSals SELECT empno, sal AS oldsal FROM Emps END; CREATE OR REPLACE TRIGGER check_sals AFTER UPDATE OF sal ON Emps BEGIN FOR erec IN (SELECT empno FROM Emps NATURAL JOIN EmpSals WHERE sal > 1.1 * oldsal) LOOP RAISE_APPLICATION_ERROR( -20032, 'Salary Increase Too Large ' ); END LOOP; Requires scanning Emps table even if END; just a single sal was updated! © Ellis Cohen 2001-2008 6 Row Triggers Statement Trigger Executes the trigger code once before or after the statement Row Trigger Executes the trigger code once before or after each row affected (inserted, updated, or deleted) Has access to the attribute values both before and after the row is changed Are problematic if they need to query or modify the table causing the trigger (we'll discuss this later) © Ellis Cohen 2001-2008 7 Using Row Triggers Reject excessive salary increases CREATE OR REPLACE TRIGGER emp_check_salary AFTER UPDATE OF sal ON Emps FOR EACH ROW CALL CheckSalary( :OLD.sal, :NEW.sal ) PROCEDURE CheckSalary( oldsal number, newsal number) IS BEGIN IF newsal > 1.1*oldsal THEN RAISE_APPLICATION_ERROR( -20032, 'Salary Increase Too Large' ); END IF; Note this will rollback the entire END; update statement; later we'll see how to just reject the excessive increases © Ellis Cohen 2001-2008 8 Audit Change Exercise CREATE TABLE ProjLog ( pno number(5), usr varchar(30), tim date ); PROCEDURE LogChange( aPno int ) IS usr varchar(30); tim date; BEGIN SELECT user, sysdate INTO usr, tim FROM dual; INSERT INTO ProjLog VALUES( aPno, usr, tim ); END; Write a trigger (or triggers): For each change (insert, update, or delete) to the Projs table, log the project affected, which user made the change, and the date/time [by calling LogChange] (assume pno is never updated in Projs) © Ellis Cohen 2001-2008 9 Audit Change Answer CREATE OR REPLACE TRIGGER audit_insupd_proj AFTER INSERT OR UPDATE ON Projs FOR EACH ROW CALL LogChange( :NEW.pno ) Can't include DELETE since :NEW.pno would be NULL! CREATE OR REPLACE TRIGGER audit_del_proj AFTER DELETE ON Projs FOR EACH ROW CALL LogChange( :OLD.pno ) Can this be done using a single trigger? © Ellis Cohen 2001-2008 10 Single Trigger Implementations CREATE OR REPLACE TRIGGER audit_proj AFTER INSERT OR UPDATE OR DELETE ON Projs FOR EACH ROW BEGIN IF :NEW.pno IS NULL THEN LogChange( :OLD.pno ); ELSE LogChange( :NEW.pno ); END IF; END; CREATE OR REPLACE TRIGGER audit_proj AFTER INSERT OR UPDATE OR DELETE ON Projs FOR EACH ROW CALL LogChange( nvl( :NEW.pno, :OLD.pno ) ) © Ellis Cohen 2001-2008 11 Using Typing Functions CREATE OR REPLACE TRIGGER audit_proj AFTER INSERT OR UPDATE OR DELETE ON Projs FOR EACH ROW BEGIN IF DELETING THEN /* only :OLD is valid */ LogChange( :OLD.pno ); ELSE LogChange( :NEW.pno ); END IF; END; © Ellis Cohen 2001-2008 12 Modification-Typing Functions DELETING Parameterless function returns true if row trigger execution caused by DELETE INSERTING Returns true if row trigger execution caused by INSERT UPDATING Returns true if row trigger execution caused by UPDATE UPDATING( 'sal' ) true if sal attribute was updated Can be used in statement triggers as well Can be used in procedures called from triggers © Ellis Cohen 2001-2008 13 Incremental Derivation Exercise Conceptual Transition Constraint A department's totsal is automatically derived and is the total salary of the employees in the department Relational Model CREATE TABLE Depts( deptno int primary key, dname varchar(12) not null, loc varchar(18), totsal int ) Can you implement this using one or more row triggers on Emps? You may assume that an employee's deptno cannot be changed. © Ellis Cohen 2001-2008 14 Updating Entire Dept CREATE OR REPLACE TRIGGER emp_del_totsal AFTER UPDATE OF sal OR INSERT OR DELETE ON Emps FOR EACH ROW DECLARE empdept int :=nvl( :OLD.deptno, :NEW.deptno ); BEGIN UPDATE Depts d SET totsal = ( SELECT sum(e.sal) FROM Emps e WHERE e.deptno = empdept) WHERE deptno = empdept; END; Only recalculates totsal of the department corresponding to the employee being modified. Unfortunately this doesn't work because Oracle doesn't allow a row trigger to use the table (i.e. Emps) on which the trigger is defined © Ellis Cohen 2001-2008 15 Incremental Derivation Solution CREATE OR REPLACE TRIGGER emp_del_totsal AFTER DELETE ON Emps FOR EACH ROW BEGIN UPDATE Depts SET totsal = totsal - :OLD.sal WHERE deptno = :OLD.deptno; END; CREATE OR REPLACE TRIGGER emp_ins_totsal AFTER INSERT ON Emps FOR EACH ROW BEGIN UPDATE Depts SET totsal = totsal + :NEW.sal WHERE deptno = :NEW.deptno; END; CREATE OR REPLACE TRIGGER emp_ins_totsal AFTER UPDATE OF sal ON Emps FOR EACH ROW BEGIN UPDATE Depts SET totsal = totsal + :NEW.sal - :OLD.sal WHERE deptno = :NEW.deptno; END; © Ellis Cohen 2001-2008 16 Using Update Procedure CREATE OR REPLACE TRIGGER emp_del_totsal AFTER DELETE ON Emps FOR EACH ROW CALL UpdateTotSal( :OLD.deptno, -:OLD.sal ) CREATE OR REPLACE TRIGGER emp_ins_totsal AFTER INSERT ON Emps FOR EACH ROW CALL UpdateTotSal( :NEW.deptno, :NEW.sal ) CREATE OR REPLACE TRIGGER emp_ins_totsal AFTER UPDATE OF sal ON Emps FOR EACH ROW CALL UpdateTotSal( :OLD.deptno, :NEW.sal-:OLD.sal ) PROCEDURE UpdateTotSal( aDeptno int, delta int ) IS BEGIN UPDATE Depts SET totsal = totsal + delta WHERE deptno = aDeptno; END; © Ellis Cohen 2001-2008 17 Using Typing Function CREATE OR REPLACE TRIGGER emp_upd_totsal AFTER INSERT OR DELETE OR UPDATE Of sal ON Emps FOR EACH ROW BEGIN IF DELETING THEN UpdateTotSal( :OLD.deptno, -:OLD.sal ); ELSIF INSERTING THEN UpdateTotSal( :NEW.deptno, :NEW.sal ); ELSE UpdateTotSal( :OLD.deptno, :NEW.sal-:OLD.sal ); END IF; END; PROCEDURE UpdateTotSal( aDeptno int, delta int ) IS BEGIN UPDATE Depts SET totsal = totsal + delta WHERE deptno = aDeptno; END; © Ellis Cohen 2001-2008 18 Using nvl CREATE OR REPLACE TRIGGER emp_upd_totsal AFTER INSERT OR DELETE OR UPDATE Of sal ON Emps FOR EACH ROW BEGIN UPDATE Depts SET totsal = totsal + nvl( :NEW.sal, 0 ) – nvl( :OLD.sal, 0 ) WHERE deptno = nvl( :NEW.deptno, :OLD.deptno ); END; Suppose an employee's deptno can be changed? © Ellis Cohen 2001-2008 19 Allowing deptno Changes CREATE OR REPLACE TRIGGER emp_upd_totsal AFTER INSERT OR DELETE OR UPDATE Of sal, deptno ON Emps FOR EACH ROW BEGIN IF NOT INSERTING THEN UpdateTotSal( :OLD.deptno, -:OLD.sal ); END IF; IF NOT DELETING THEN UpdateTotSal( :NEW.deptno, :NEW.sal ); END IF; END; This requires two updates even if deptno doesn’t change © Ellis Cohen 2001-2008 20 Better Derivation Solution CREATE OR REPLACE TRIGGER emp_upd_totsal AFTER INSERT OR DELETE OR UPDATE Of sal, deptno ON Emps FOR EACH ROW BEGIN IF UPDATING AND (:OLD.deptno = :NEW.deptno) THEN UpdateTotSal( :OLD.deptno, :NEW.sal-:OLD.sal ); ELSE IF NOT INSERTING THEN UpdateTotSal( :OLD.deptno, -:OLD.sal ); END IF; IF NOT DELETING THEN UpdateTotSal( :NEW.deptno, :NEW.sal ); END IF; END IF; END; © Ellis Cohen 2001-2008 21 Using Row Triggers for Enforcing State Constraints © Ellis Cohen 2001-2008 22 Maintain Salary Within Limits Define a table SalGrades( job, minsal, maxsal ) which has the minimum & maximum salary allowed for each job Enforce the state constraint: Any employee whose job is in SalGrades has a salary between the corresponding minsal and maxsal NOT EXISTS ( Emps NATURAL JOIN SalGrades WHERE sal NOT BETWEEN minsal AND maxsal) © Ellis Cohen 2001-2008 23 Using Statement Triggers CREATE TRIGGER check_salgrade_change AFTER UPDATE OR INSERT ON SalGrades CALL CheckSals CREATE TRIGGER check_emp_change AFTER UPDATE OF sal, job OR INSERT ON Emps CALL CheckSals PROCEDURE CheckSals IS BEGIN FOR erec IN (SELECT empno FROM Emps NATURAL JOIN SalGrades WHERE sal NOT BETWEEN minsal AND maxsal) LOOP RAISE_APPLICATION_ERROR( -20111, 'Salaries do not match ranges' ); END LOOP; END; © Ellis Cohen 2001-2008 24 Using Row Triggers Statement triggers are fine if employees' salaries are frequently changed in bulk. But if individual employee's salaries are changed frequently, a row trigger will make more sense for Emps, and can use a specialized checking routine. CREATE OR REPLACE TRIGGER check_emp_change AFTER UPDATE OF sal, job OR INSERT ON Emps FOR EACH ROW Call CheckEmpChange( :NEW.job, :NEW.sal ) © Ellis Cohen 2001-2008 25 Optimized Emps Checking CREATE OR REPLACE TRIGGER check_emp_change AFTER UPDATE OF sal, job OR INSERT ON Emps FOR EACH ROW Call CheckEmpChange( :NEW.job, :NEW.sal ) PROCEDURE CheckEmpChange( newjob varchar, newsal number ) IS mnsal SalGrades.minsal%type; mxsal SalGrades.maxsal%type; BEGIN RETURN if job not in SalGrades BEGIN SELECT minsal, maxsal INTO mnsal, mxsal FROM SalGrades WHERE job = newjob; EXCEPTION WHEN OTHERS THEN RETURN; END; IF (newsal < mnsal) OR (newsal > mxsal) THEN RAISE_APPLICATION_ERROR(-20111, 'Bad Salary' ); END IF ; END; © Ellis Cohen 2001-2008 26 Optimized SalGrades Checking CREATE OR REPLACE Trigger check_salgrade_change AFTER INSERT OR UPDATE ON SalGrades FOR EACH ROW CALL CheckSalGradeChange( :OLD, :NEW ) PROCEDURE CheckSalGradeChange( oldg SalGrades%ROWTYPE, Not yet newg SalGrades%ROWTYPE ) IS supported; need to pass individual numemps int := 0; attributes BEGIN IF (oldg.job = newg.job AND newg.minsal <= oldg.minsal AND newg.maxsal <= oldg.maxsal) THEN RETURN; END IF; SELECT count(*) INTO numemps FROM Emps WHERE job = newg.job AND sal NOT BETWEEN newg.minsal AND newg.maxsal; IF (numemps > 0) THEN RAISE_APPLICATION_ERROR( -20111, 'Bad Salary' ); END IF ; END; © Ellis Cohen 2001-2008 27 When to Optimize Optimize when – Actions are likely to only modify a small # of employees at a time • Otherwise, CheckSals, even though it does a join, will be more efficient – Modifications are frequent • Otherwise, there is little to be gained, and there is more code, and more complex code, to maintain – When a group of changes are made, you want to reject the problematic ones while allowing the rest to be done © Ellis Cohen 2001-2008 28 State Assertion Support Assertions (using SQL-92's CREATE ASSERTION) could be mapped automatically to triggers. So why don’t commercial databases implement CREATE ASSERTION? Besides the fact that the mapping is hard to implement, how would the database decide whether to use statement triggers or row triggers? © Ellis Cohen 2001-2008 29 Application-Based Enforcement When using application-based enforcement, these procedures can be explicitly by operations providing operation-dependent enforcement: For ChangeSal( :empno, :sal ) ChangeJob( :empno, :job ) AddEmp( :empno, :ename, :sal, :job ) have these procedures explicitly call CheckEmpChange after they finish making the change Whereas ChangeSalgrade( :grade, :mnsal, :mxsal ) will explicitly call CheckSals © Ellis Cohen 2001-2008 30 BEFORE Row Triggers © Ellis Cohen 2001-2008 31 Tuple-Based Correction Suppose we wanted to use correction to enforce the constraint that no employee makes > 5000 by changing such an employee's salary back to 5000 CREATE OR REPLACE TRIGGER limit_sal AFTER UPDATE OF sal OR INSERT ON Emps BEGIN UPDATE Emps SET sal = 5000 WHERE sal > 5000 END; If only one employee has their salary changed, the trigger still involves scanning all the employees! © Ellis Cohen 2001-2008 32 Using BEFORE ROW Triggers BEFORE ROW triggers are checked • After the contents of the new row are calculated • Before the row is stored in the table You must use BEFORE if you want to modify a value in a row being inserted or updated CREATE OR REPLACE TRIGGER limit_salary BEFORE UPDATE OF sal OR INSERT ON Emps FOR EACH ROW BEGIN IF :NEW.sal > 5000 THEN :NEW.sal := 5000; END IF; END; If an inserted/updated employee gets a sal > 5000, then change its sal back to 5000 © Ellis Cohen 2001-2008 33 WHEN Limits Trigger Execution WHEN can be used with (BEFORE or AFTER) row triggers to limit circumstances in which the trigger code is executed, improving performance CREATE OR REPLACE TRIGGER limit_salary BEFORE INSERT OR UPDATE OF sal ON Emps FOR EACH ROW WHEN (new.sal > 5000) BEGIN :NEW.sal := 5000; END; How would you prevent salary increases of more than 10% by correction? © Ellis Cohen 2001-2008 34 Correct Excessive Salary Increases CREATE OR REPLACE TRIGGER emp_check_salary BEFORE UPDATE OF sal ON Emps FOR EACH ROW WHEN (new.sal > 1.1 * old.sal) BEGIN :NEW.sal := 1.1 * :OLD.sal; END; © Ellis Cohen 2001-2008 35 Derived Values Exercise Given a relational model (in SQL SERVER) CREATE TABLE Orders( orderid int primary key, subtotal number(8,2), shipCost number(5,2), total number(9,2) AS subtotal + shipCost ) Show the row trigger you would use to enforce the derivation constraint © Ellis Cohen 2001-2008 36 Derived Values Answer CREATE OR REPLACE TRIGGER set_total BEFORE INSERT OR UPDATE OF subtotal, shipCost ON Orders FOR EACH ROW BEGIN :NEW.total := :NEW.subtotal + :NEW.shipCost; END; But what happens if an attempt is made to change an order's total? © Ellis Cohen 2001-2008 37 Derivation with Correction CREATE OR REPLACE TRIGGER set_total BEFORE INSERT OR UPDATE OF total, subtotal, shipCost ON Orders FOR EACH ROW BEGIN :NEW.total := :NEW.subtotal + :NEW.shipCost; END; UPDATE of total automatically resets total correctly if an attempt is made to change it © Ellis Cohen 2001-2008 38 Derivation with Rejection CREATE OR REPLACE TRIGGER set_total BEFORE INSERT OR UPDATE OF subtotal, shipCost ON Orders FOR EACH ROW BEGIN :NEW.total := :NEW.subtotal + :NEW.shipCost; END; CREATE OR REPLACE TRIGGER no_total_change BEFORE UPDATE OF total ON Orders FOR EACH ROW BEGIN RAISE_APPLICATION_ERROR( … ); END; Suppose we wanted to raise an error when a user changed a total. Why wouldn't this work? © Ellis Cohen 2001-2008 39 Triggers Can Trigger CREATE OR REPLACE TRIGGER set_total BEFORE INSERT OR UPDATE OF subtotal, shipCost ON Orders FOR EACH ROW BEGIN :NEW.total := :NEW.subtotal + :NEW.shipCost; END; CREATE OR REPLACE TRIGGER no_total_change BEFORE UPDATE OF total ON Orders FOR EACH ROW BEGIN RAISE_APPLICATION_ERROR( … ); END; When an order's subtotal is changed, set_total changes its total. This triggers no_total_change, which raises an error. It prevents indirect as well as direct changes to total © Ellis Cohen 2001-2008 40 Setting Ids Automatically Given a relational model with Asns asnid empno pno hrs SQL SERVER only int primary key identity int references Emps int references Projs int We would like to allow code to INSERT into Asns using just INSERT INTO Asns( empno, pno, hrs ) VALUES ( 30450, 2247, 12 ) And have asnid automatically filled in. SQL Server supports the identity property which does this automatically. How could this be supported in Oracle using a trigger? © Ellis Cohen 2001-2008 41 Setting Ids Using Triggers CREATE SEQUENCE asnseq; -- Defines a sequence used to generate -- sequential values CREATE OR REPLACE trigger set_asnid BEFORE INSERT ON Asns FOR EACH ROW BEGIN SELECT asnseq.nextval INTO :NEW.asnid FROM DUAL; ----- END; nextval is a function which gets the next value from a sequence, but can ONLY be called in a query. DUAL is a convenient, built-in single row/column table Exercise: Change the code to allow a user to optionally provide their own id, but deal with the case when a user or automatically generated id is already used in Asns © Ellis Cohen 2001-2008 42 Trigger Evaluation Order When a DELETE/INSERT/UPDATE/MERGE is executed All BEFORE Statement triggers are executed (in undetermined order) As each row is modified All BEFORE Row triggers are executed (in undetermined order) All AFTER Row triggers are executed (in undetermined order) All AFTER Statement triggers are executed (in undetermined order) Sometimes you can pick trigger types to ensure that some triggers run before others © Ellis Cohen 2001-2008 43 Collecting Modifications for AFTER Statement Triggers © Ellis Cohen 2001-2008 44 Inserted & Deleted Tables SQL Server can automatically keep track of the modifications made in a table for use in an after statement trigger – A temporary Deleted table keeps track of the :OLD values of every tuple updated or deleted – A temporary Inserted table keeps track of the :NEW values of every tuple updated or inserted In Oracle, these tables can be built using row triggers © Ellis Cohen 2001-2008 45 Building Inserted & Deleted CREATE GLOBAL TEMPORARY TABLE Deleted AS SELECT * FROM Emps WHERE empno IS NULL; CREATE OR REPLACE TRIGGER oldemps AFTER UPDATE OR DELETE ON Emps FOR EACH ROW BEGIN INSERT INTO EmpsDeleted VALUES :OLD; END CREATE GLOBAL TEMPORARY TABLE Inserted AS SELECT * FROM Emps WHERE empno IS NULL; CREATE OR REPLACE TRIGGER oldemps AFTER UPDATE OR INSERT ON Emps FOR EACH ROW BEGIN INSERT INTO EmpsInserted VALUES :NEW; END Not quite legal: actually need to use VALUES( :NEW.empno, …); ditto for :OLD © Ellis Cohen 2001-2008 46 Incremental Derivation Exercise Conceptual Transition Constraint A department's totsal is automatically derived and is the total salary of the employees in the department Relational Model CREATE TABLE Depts( deptno int primary key, dname varchar(12) not null, loc varchar(18), totsal int ) Can you implement the transition constraint using an AFTER statement trigger, assuming that Inserted and Deleted have been defined? © Ellis Cohen 2001-2008 47 Derivation Solution #1 CREATE OR REPLACE TRIGGER emp_upd_totsals AFTER INSERT OR DELETE OR UPDATE OF sal, deptno ON Emps BEGIN UPDATE Depts d SET totsal = totsal + nvl((SELECT sum(sal) FROM Inserted e WHERE e.deptno = d.deptno),0) - nv((SELECT sum(sal) FROM Deleted e WHERE e.deptno = d.deptno),0) END; © Ellis Cohen 2001-2008 48 Derivation Solution #2 CREATE OR REPLACE TRIGGER emp_upd_totsals AFTER INSERT OR DELETE OR UPDATE OF sal, deptno ON Emps BEGIN UPDATE Depts d SET totsal = (SELECT sum(sal) FROM Emps e WHERE e.deptno = d.deptno) WHERE d IN ( SELECT deptno FROM Inserted UNION SELECT deptno FROM Deleted) END; Recompute totsal in the affected departments © Ellis Cohen 2001-2007 49 Row Triggers Collect Information Row triggers can be used more generally to collect information for AFTER statement triggers. 1. As each modified row is processed, add (via an AFTER row trigger) specific information to a temporary table. 2. The AFTER statement trigger uses the information in the temporary table. Each session has its own copy of the temporary table. At the end of each transaction, the table is emptied © Ellis Cohen 2001-2007 50 Using Tailored Temporary Tables CREATE GLOBAL TEMPORARY TABLE AffectedDepts( deptno int primary key ); CREATE OR REPLACE TRIGGER affected_sal_depts AFTER UPDATE OF sal, deptno OR INSERT OR DELETE ON Emps FOR EACH ROW BEGIN INSERT INTO AffectedDepts VALUES( nvl( :NEW.deptno, :OLD.deptno ) ); EXCEPTION -- an exception will be raised if the dept has -- already been included, which we can ignore WHEN OTHERS THEN NULL; END; Keep track of the depts whose employee salaries have been affected © Ellis Cohen 2001-2007 51 Update Employees in Affected Depts CREATE OR REPLACE TRIGGER emp_upd_totsals AFTER INSERT OR DELETE OR UPDATE Of sal, deptno ON Emps BEGIN UPDATE Depts d SET totsal = (SELECT sum(sal) FROM Emps e WHERE e.deptno = d.deptno) WHERE d IN (SELECT * FROM AffectedDepts); END; Only need to update totsal in the affected departments © Ellis Cohen 2001-2007 52 Querying Triggered Tables with Row Triggers © Ellis Cohen 2001-2008 53 Statement Trigger Enforcement Two employees in same dept may not have the same job CREATE OR REPLACE TRIGGER diff_jobs AFTER UPDATE OF deptno, job OR INSERT ON Emps CALL CheckSameJob PROCEDURE CheckSameJob IS BEGIN FOR rec IN ( SELECT deptno, job FROM Emps GROUP BY deptno, job HAVING count(*) > 1) LOOP RAISE_APPLICATION_ERROR( -20099, 'Move than one employee in dept ' || rec.deptno || ' has job ' || rec.job ); END LOOP; END; Big performance hit if operation only impacts a single employee! © Ellis Cohen 2001-2008 54 Modification-Based Optimization Statement triggers can have a high performance overhead if – they require table scans – actions usually only modify a few tuples Row triggers can give better performance © Ellis Cohen 2001-2008 55 Row Trigger Enforcement for INSERT +State constraint: Two employees in the same dept may not have the same job CREATE OR REPLACE TRIGGER ins_emp AFTER INSERT ON Emps FOR EACH ROW CALL CheckOnAddEmp( :NEW.deptno, :NEW.job ) CheckOnAddEmp will have to determine whether adding an employee to the department will cause a problem – i.e. it will have to determine whether there is already an employee in the dept that has the same job © Ellis Cohen 2001-2008 56 Possible Code for CheckOnAddEmp PROCEDURE CheckOnAddEmp( newdept int, newjob varchar ) IS BEGIN FOR rec IN ( SELECT deptno, job FROM Emps WHERE deptno = newdept AND job = newjob ) LOOP RAISE_APPLICATION_ERROR( -20099, 'An employee in dept ' || rec.deptno || ' already has job ' || rec.job ) END LOOP; END; This could run very efficiently if (a) only a few employees are affected, and (b) if Emps has an index on dept and/or job. Unfortunately this doesn't work because Oracle doesn't allow a row trigger's code to use the table (i.e. Emps) on which the trigger is defined © Ellis Cohen 2001-2008 57 Mutating Table Error (1) A row trigger executed in the middle of some change to a table (2) A query (or update) of the same table by the row trigger code This situation can lead to inconsistent results – the query in the row trigger code will some of the modified rows, but the order of modification is not guaranteed so this causes a Mutating Table Error in Oracle (but not in SQL Server) © Ellis Cohen 2001-2008 58 Using Autonomous Transactions If the row trigger accesses the triggering table in an autonomous transaction, there's no mutating table error. Because the autonomous transaction will not see changes made by the triggering operation (since it has not committed), it will not see any changes made by the rows that have already been updated or the row itself. © Ellis Cohen 2001-2008 59 Revised Code for CheckOnAddEmp +State constraint: Two employees in the same dept may not have the same job PROCEDURE CheckOnAddEmp( newdept int, newjob varchar ) IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN FOR rec IN ( SELECT deptno, job FROM Emps WHERE deptno = newdept AND job = newjob ) LOOP ROLLBACK; RAISE_APPLICATION_ERROR( -20099, 'An employee in dept ' || rec.deptno || ' already has job ' || rec.job ) END LOOP; COMMIT; END; © Ellis Cohen 2001-2008 60 COMMIT & ROLLBACK Before returning from a procedure which is associated with an autonomous transaction, you need to complete the transaction via COMMIT or ROLLBACK, otherwise the transaction will never finish (which can affect performance) Since the procedure only reads data and doesn’t change anything, it doesn't matter whether COMMIT or ROLLBACK is used Just as a convention we use ROLLBACK if an exception is raised, and COMMIT if the procedure returns normally © Ellis Cohen 2001-2008 61 Problems of Using Autonomous Transactions Even if each row trigger checks that each individual change doesn’t violate the state constraint, a sequence of changes could still do so. For example, suppose there were an operation that inserted two employees into the same department with the same job. With autonomous transactions, the row trigger checking each of them would only see the state before either of them ran, so the state constraint would NOT be enforced! © Ellis Cohen 2001-2008 62 Row Triggers Can Collect Information ROW triggers can be used just to collect information about tuples that have been modified, – Because there's no need to look through the table being modified (all info comes from :NEW and :OLD), there's no need for autonomous transactions A single AFTER statement trigger can then be run once – because it has access to the tuples that were modified, the checking it does can be much more efficient – Because it’s a statement trigger, there's no need for autonomous transactions © Ellis Cohen 2001-2008 63 Use Row Trigger Collection As each row involving an employee is processed (via an AFTER row trigger ON Emps), add the affected deptno/job to a temporary table. CREATE GLOBAL TEMPORARY TABLE AffectedDeptJobs ( deptno int, job varchar(20), primary key( deptno, job ) ); CREATE OR REPLACE TRIGGER rem_dept_job AFTER UPDATE OF deptno, job OR INSERT ON Emps FOR EACH ROW BEGIN INSERT INTO AffectedDeptJobs VALUES( :NEW.deptno, :NEW.job ); EXCEPTION WHEN OTHERS THEN NULL; -- an exception will be raised if the dept/job has -- already been included, which we can ignore END; © Ellis Cohen 2001-2008 64 Enforcement Trigger CREATE OR REPLACE TRIGGER diff_jobs AFTER UPDATE OF deptno, job OR INSERT ON Emps CALL CheckSameJob PROCEDURE CheckSameJob IS BEGIN FOR rec IN ( SELECT deptno, job FROM (Emps NATURAL JOIN AffectedDeptJobs) GROUP BY deptno, job HAVING count(*) > 1) LOOP RAISE_APPLICATION_ERROR( -20099, 'Move than one employee in dept ' || rec.deptno || ' has job ' || rec.job ); END LOOP; This can run efficiently if END; Emps is indexed by deptno & job © Ellis Cohen 2001-2008 65 Modifying Triggered Tables with Row Triggers © Ellis Cohen 2001-2008 66 Relational Transition Constraint Whenever a department loses its manager, all the employees who reported to that manager end up reporting to the president (employee #7839) instead. DELETE OR UPDATE OF jobs, deptno ON Emps RESULTS IN EACH Emps& m WHERE m.job' = 'DEPTMGR' AND m.job@ != 'DEPTMGR' SATISFIES EACH Emps& e WHERE e.mgr' = m.empno' SATISFIES e.mgr@ = 7839 © Ellis Cohen 2001-2008 67 Self Modification Example Suppose that we want to use triggers to ensure that whenever a department loses its dept manager, all the employees who reported to that dept manager, end up reporting to the president (employee #7839) instead Table-Based Constraint Context: UPDATE OF job, deptno OR DELETE ON Emps © Ellis Cohen 2001-2008 68 Updating Triggering Table Suppose that whenever a department loses its manager, all the employees who report to that manager now report to the president (#7839) instead. CREATE OR REPLACE TRIGGER emp_mgr AFTER UPDATE OF job, deptno OR DELETE ON Emps FOR EACH ROW WHEN ( old.job = 'DEPTMGR' ) BEGIN UPDATE Emps SET mgr = 7839 WHERE mgr = :OLD.empno; END; Doesn't work in Oracle: A transaction can't query or modify a table from a ROW TRIGGER for a row in the same table © Ellis Cohen 2001-2008 69 Modification & Autonomous Transactions Suppose the code of a table's row trigger tries to update the same table. Oracle does not allow this (i.e. raises a Mutating Table error) even though it sometimes might be reasonable. Using an Autonomous Transaction can be dangerous, since its effects won't be rolled back, if the original update fails (or the main transaction is rolled back for any reason). It can also cause deadlock (if it tries to change a tuple changed by the triggering operation) It is safer to use an application-based implementation or use the row trigger to just collect information used by an AFTER statement trigger that performs the necessary modification © Ellis Cohen 2001-2008 70 Using Row Trigger Collection CREATE GLOBAL TEMPORARY TABLE ExDeptMgrs( deptmgr int ); 1. As each row involving an employee is processed (via an AFTER row trigger ON Emps), add the empno of each affected dept manager to the temporary table. 2. Use an AFTER statement trigger ON Emps to update the mgrs of all employees whose mgrs are in the temporary table. © Ellis Cohen 2001-2008 71 Collection Trigger As each row involving an employee is processed (via an AFTER row trigger ON Emps), add the empno of each affected dept manager to the temporary table. CREATE OR REPLACE TRIGGER mgr_del AFTER UPDATE OF job, deptno OR DELETE ON Emps FOR EACH ROW WHEN (old.job = 'DEPTMGR') BEGIN INSERT INTO ExDeptMgrs VALUES( :OLD.empno ); END; © Ellis Cohen 2001-2008 72 Correction Trigger Use an AFTER statement trigger ON Emps to update the mgrs of all employees whose mgrs are in the temporary table. CREATE OR REPLACE TRIGGER mgr_del_final AFTER UPDATE OF job, deptno OR DELETE ON Emps BEGIN UPDATE Emps SET mgr = 7839 WHERE mgr IN (SELECT * FROM ExDeptMgrs) END; © Ellis Cohen 2001-2008 73 Phased Constraint Enforcement In general, whether using • trigger-based or • application-based enforcement, complex state/transition constraints can require 1a) Collect initial information and/or 1b) Collect information about each change made 2) At the end of the action, do the main work (rejection or correction to satisfy some constraint) © Ellis Cohen 2001-2008 74 Cascading Row Triggers © Ellis Cohen 2001-2008 75 The Derivation Problem Conceptual Transition Constraint A department's totsal is automatically derived and is the total salary of the employees in the department CREATE OR REPLACE TRIGGER emp_upd_totsals AFTER UPDATE OF sal, deptno OR INSERT OR DELETE ON Emps BEGIN UPDATE Depts d SET totsal = (SELECT sum(sal) FROM Emps e WHERE e.deptno = d.deptno); END; Suppose some user action tries to explicitly set a department's totsal © Ellis Cohen 2001-2008 76 Handling Improper Changes Suppose some user action tries to explicitly set a department's totsal Two alternatives 1. Reject: Raise an error (which could undo the entire user action) 2. Correct: Just recalculate totsal based on the employee salaries © Ellis Cohen 2001-2008 77 Attempted Derivation with Rejection CREATE OR REPLACE TRIGGER emp_upd_totsals AFTER UPDATE OF sal, deptno OR INSERT OR DELETE ON Emps BEGIN UPDATE Depts d SET totsal = (SELECT sum(sal) FROM Emps e WHERE e.deptno = d.deptno); END; CREATE OR REPLACE TRIGGER dept_upd_totsals BEFORE UPDATE Of totsal ON Depts FOR EACH ROW BEGIN RAISE_APPLICATION_ERROR( … ); END; What happens when an employee's salary is changed? © Ellis Cohen 2001-2008 78 Trigger Cascade with Rejection When an employee's salary is updated, this trigger runs CREATE OR REPLACE TRIGGER emp_upd_totsals AFTER UPDATE Of sal, deptno OR INSERT OR DELETE OR ON Emps BEGIN which updates UPDATE Depts d SET totsal = all dept totsals (SELECT sum(sal) FROM Emps e WHERE e.deptno = d.deptno); Trigger END; Which causes this trigger to run for each dept! Cascade CREATE OR REPLACE TRIGGER dept_upd_totsals BEFORE UPDATE Of totsal ON Depts FOR EACH ROW But on the first dept, BEGIN this error RAISE_APPLICATION_ERROR( … ); undoes END; everything! © Ellis Cohen 2001-2008 79 Trigger Cascades If the code executed for a trigger modifies some table, that trigger will immediately be run (as if its code were a nested procedure). If the cascaded trigger raises an error, it will be propagated through the initial trigger code. To avoid raising the error, trigger code needs to determine whether it was executed as a result of another trigger. This can be done using triggers protected by package variables. © Ellis Cohen 2001-2008 80 Avoiding Cascade Trigger Effects CREATE OR REPLACE PACKAGE HelpTotsal AS updateOK boolean := FALSE; END HelpTotsal; CREATE OR REPLACE TRIGGER emp_upd_totsals AFTER UPDATE Of sal, deptno OR INSERT OR DELETE OR ON Emps BEGIN To be really safe; HelpTotsal.updateOK := TRUE; we'd need to catch UPDATE Depts d SET totsal = any exception due (SELECT sum(sal) FROM Emps e to the UPDATE, set updateOK to false & WHERE e.deptno = d.deptno); reraise the HelpTotal.updateOK := FALSE; exception END; CREATE OR REPLACE TRIGGER dept_upd_totsals BEFORE UPDATE Of totsal ON Depts FOR EACH ROW BEGIN IF NOT HelpTotsal.updateOK THEN RAISE_APPLICATION_ERROR( … ); END IF; END; © Ellis Cohen 2001-2008 81 Fixing Exception Handling CREATE OR REPLACE TRIGGER emp_upd_totsals AFTER UPDATE Of sal, deptno OR INSERT OR DELETE OR ON Emps BEGIN HelpTotsal.updateOK := TRUE; UPDATE Depts d SET totsal = (SELECT sum(sal) FROM Emps e WHERE e.deptno = d.deptno); HelpTotal.updateOK := FALSE; EXCEPTION WHEN OTHERS THEN HelpTotal.updateOK := FALSE; RAISE; END; CREATE OR REPLACE TRIGGER dept_upd_totsals BEFORE UPDATE Of totsal ON Depts FOR EACH ROW BEGIN IF NOT HelpTotsal.updateOK THEN RAISE_APPLICATION_ERROR( … ); END IF; END; © Ellis Cohen 2001-2008 82 Derivation with Correction CREATE OR REPLACE TRIGGER emp_upd_totsals AFTER UPDATE Of sal, deptno OR INSERT OR DELETE OR ON Emps BEGIN HelpTotsal.updateOK := TRUE; UPDATE Depts d SET totsal = (SELECT sum(sal) FROM Emps e WHERE e.deptno = d.deptno); HelpTotal.updateOK := FALSE; EXCEPTION WHEN OTHERS THEN HelpTotal.updateOK := FALSE; RAISE; END; CREATE OR REPLACE TRIGGER dept_upd_totsals BEFORE UPDATE Of totsal ON Depts FOR EACH ROW BEGIN IF NOT HelpTotsal.updateOK THEN :NEW.totsal := :OLD.totsal; END IF; END; © Ellis Cohen 2001-2008 83 Using Incremental Derivation CREATE OR REPLACE TRIGGER emp_upd_totsal AFTER UPDATE Of sal, deptno OR INSERT OR DELETE OR ON Emps FOR EACH ROW BEGIN HelpTotsal.updateOK := TRUE; IF UPDATING AND (:OLD.deptno = :NEW.deptno) THEN UpdateTotSal( :OLD.deptno, :NEW.sal-:OLD.sal ); ELSE IF NOT INSERTING THEN UpdateTotSal( :OLD.deptno, -:OLD.sal ); END IF; IF NOT DELETING THEN UpdateTotSal( :NEW.deptno, :NEW.sal ); END IF; END IF; HelpTotsal.updateOK := FALSE; EXCEPTION WHEN OTHERS THEN HelpTotal.updateOK := FALSE; RAISE; END; © Ellis Cohen 2001-2008 84 Alternate Derivation with Correction CREATE OR REPLACE TRIGGER emp_upd_totsals AFTER UPDATE Of sal, deptno OR INSERT OR DELETE OR ON Emps FOR EACH ROW BEGIN UPDATE Depts d SET totsal = NULL WHERE deptno = :OLD.deptno OR deptno = :NEW.deptno END; In the collection phase, set the totsal to NULL if an employee's sal in that dept has changed! © Ellis Cohen 2001-2008 85 Enforcement CREATE OR REPLACE TRIGGER dept_upd_totsals AFTER UPDATE Of totsal ON Depts BEGIN HelpTotsal.updateOK := TRUE; UPDATE Depts d SET totsal = (SELECT sum(sal) FROM Emps e WHERE e.deptno = d.deptno) WHERE totsal IS NULL; HelpTotsal.updateOK := FALSE; EXCEPTION WHEN OTHERS THEN HelpTotal.updateOK := FALSE; RAISE; END; © Ellis Cohen 2001-2008 86 Handle Changes to totsal If any attempt is made to change totsal (except to NULL), set it to NULL as well CREATE OR REPLACE TRIGGER dept_fix_totsals BEFORE UPDATE Of totsal ON Depts FOR EACH ROW WHEN (new.totsal IS NOT NULL) BEGIN IF NOT HelpTotsal.updateOK THEN :NEW.totsal := :OLD.totsal; END IF; END; © Ellis Cohen 2001-2008 87 INSTEAD OF Triggers © Ellis Cohen 2001-2008 88 INSTEAD Of Triggers INSTEAD OF Triggers Provide code to be executed INSTEAD OF the statement being monitored Used for views (not tables), especially ones which would otherwise be non-updateable. © Ellis Cohen 2001-2008 89 Non-Updatable Views CREATE VIEW EmpByJob AS SELECT job, count(*) AS njob FROM Emps GROUP BY job JOB NJOB --------- ----ANALYST 2 CLERK 4 MANAGER 3 PRESIDENT 1 SALESMAN 4 Goal: Deleting a row from this view has the effect of deleting all the employees in Emps with the specified job © Ellis Cohen 2001-2008 90 Using INSTEAD OF Triggers for Non-Updatable Views CREATE TRIGGER empjob_del INSTEAD OF DELETE ON EmpByJob FOR EACH ROW BEGIN DELETE FROM Emps WHERE job = :OLD.job ; END; © Ellis Cohen 2001-2008 91 Updating Triggering Table Suppose that whenever a department loses its manager, all the employees who report to that manager now report to the president (#7839) instead. CREATE OR REPLACE TRIGGER emp_mgr AFTER DELETE OR UPDATE OF job, deptno ON Emps Limits FOR EACH ROW TRIGGER WHEN ( old.job = 'DEPTMGR' ) calls BEGIN UPDATE Emps SET mgr = 7839 WHERE mgr = :OLD.empno; END; Doesn't work in Oracle: Can't query or modify a table whose row is being changed © Ellis Cohen 2001-2008 92 Use Intermediate Views CREATE VIEW EmpsView AS SELECT * FROM Emps Create a view and ensure that all actions which want to modify Emps actually modify EmpsView (which is updateable) instead. ROW Triggers on the view are allowed to modify the underlying table © Ellis Cohen 2001-2008 93 Trigger on the View CREATE OR REPLACE TRIGGER emp_delupd INSTEAD OF DELETE OR UPDATE OF job, deptno ON EmpsView FOR EACH ROW BEGIN IF (:OLD.job = 'DEPTMGR') THEN UPDATE Emps SET mgr = 7839 WHERE mgr = :OLD.empno; Must do the actual DELETE END IF; & UPDATE, IF DELETING THEN since the DELETE FROM Emps trigger is WHERE empno = :OLD.empno; INSTEAD OF ELSE UPDATE Emps SET ROW = :NEW WHERE empno = :OLD.empno; END; Not yet supported. Must actually do attribute-by-attribute © Ellis Cohen 2001-2008 94 Implementing Materialized Views © Ellis Cohen 2001-2008 95 Cost of Using Views Typically a view involves joins across multiple tables. Every time a view is queried, a join must be done. This can be expensive if the view is used frequently Suppose – The view is used frequently – The underlying tables on which the view is based do not change very often © Ellis Cohen 2001-2008 96 Example View CREATE VIEW EmpDeptsView AS SELECT empno, ename, job, deptno, dname FROM Emps NATURAL JOIN Depts • Suppose Emps and Depts are changed infrequently • Everytime EmpDeptsView is accessed, Emps and Depts have to be joined © Ellis Cohen 2001-2008 97 Materialized Views A materialized view is an actual persistent table which contains the data to be viewed from underlying tables. Every time the underlying tables are changed, the materialized view must also be changed to keep it consistent (sometimes this is done on a regular schedule instead of on every change) Materialized Views are frequently read only. Sometimes they support updates propagated back to their base tables. © Ellis Cohen 2001-2008 98 Defining Materialized Views CREATE MATERIALIZED VIEW EmpDeptsView BUILD IMMEDIATE REFRESH COMPLETE AS SELECT empno, ename, job, deptno, dname FROM Emps NATURAL JOIN Depts EmpDeptView is now a table that will be built immediately, and completely updated whenever a change is made in the underlying base tables (Oracle only) Alternate specifications allow • Deferred initial build • Incremental update • Interval-based refresh © Ellis Cohen 2001-2008 99 Maintaining a Materialized View CREATE TABLE MatEmpDepts AS SELECT * FROM EmpDeptsView Databases implement materialized views by defining a table for the materialized view CREATE TRIGGER mat_emp AFTER UPDATE OF ename, job, deptno OR INSERT OR DELETE ON Emps CALL UpdateMatView CREATE TRIGGER mat_dept AFTER UPDATE OF dname OR INSERT OR DELETE ON Depts CALL UpdateMatView PROCEDURE UpdateMatView IS BEGIN DELETE * FROM MatEmpDepts; INSERT INTO MatEmpDepts SELECT * FROM EmpDeptsView; END; © Ellis Cohen 2001-2008 Triggers can be used to update the materialized view table when the base tables change Exercise Optimize this using row triggers 100 Updating Materialized Views Exercise Allow MatEmpDepts to be updated 1) Propagate changes in MatEmpDepts back to Emps (but not Depts) 2) Figure out how to prevent trigger loops: A change in MatEmpDepts triggers an update of Emps, which triggers an update of MatEmpDepts 3) Keep MatEmpDepts consistent (don't allow deptno and dname to be updated unless updated together in a consistent way) © Ellis Cohen 2001-2008 101
© Copyright 2024 Paperzz