slides

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