| Course Documents |
| Current Location: Chapters 8--9 : Triggers | ||||||||||||||||||||||||||||||
| [ Top ] : Chapters 8--9 : Triggers | ||||||||||||||||||||||||||||||
![]() | Chapter 8-9 : Triggers
Setup
ConceptsA trigger is an block of PL/SQL which is invoked implicitly whenever a triggering action occurs. For example, a trigger could fire whenever any user attempts to insert data into a specific table. In this example, a trigger associates a block of PL/SQL with an DML statement on the EMP table.
Whenever any user attempts to insert data into the EMP table, this block of code will be executed. If it is not during working hours, the trigger will raise an application error, and the insert will fail. Trigger Structure / Syntax
Conditional predicatesYou can reference the conditional predicates INSERTING, DELETING, and UPDATING to determine what kind of the statement fired the trigger. The conditional predicates behave like a Boolean constant within the trigger body. The example above is extended to use conditional predicates. (Note that like an anonymous block, the trigger body begins with the DECLARE keyword.)
Statement triggers vs. Row triggersA DML trigger is either a statement trigger or a row trigger. Statement triggers fire once for the triggering statement. Row triggers fire once for each affected row. By default, triggers are statement triggers. (The triggers defined above were statement triggers.) You can specify a row trigger with the clause FOR EACH ROW.
This trigger will fire for each row that is affected by the DML statement. If the DML statement affects one row, the trigger fires once. If it affects 4 rows, the trigger fires 4 times.
Trigger TimingKeep in mind, each DML statement can fire four distinct types of triggers:
The following set of triggers prints results whenever it fires.
Also keep in mind that statement triggers will fire even if no rows are affected. SQL> UPDATE emp SET sal=sal*1.1 WHERE job='DOCTOR'; Before statement [UPDATE] After statement 0 rows updated. You can have more than one trigger with the same timing. For example, two triggers may fire BEFORE INSERT ON EMP FOR EACH ROW. You have no control over which order Oracle fires multiple triggers with the same timing. Row trigger conceptsNEW and OLDRow triggers have access to the the values of the affected row before and after the triggering statement is applied. Examine the triggering statement in the example above: UPDATE emp SET sal=sal*1.1 WHERE deptno=10; Every DML statement can be thought of as an action (UPDATE SET sal=sal*1.1) applied to a set of rows (emp WHERE deptno=10).
When Oracle interprets a DML statement it first assembles the set and iterates over the set applying changes to all rows in the set.
If it reaches the end of the set without error, then the statement completes and the changes are applied to the database. This ensures that each DML statement is atomic, meaning that each statement succeeds, or fails, but never aborts in the middle of the operation (which would leave the data in an inconsistent state).
Row triggers have access to the values in each row before and after the statement is applied to that row. First Oracle creates bind variable records inside the server memory; they are called NEW and OLD. The structure of these records is based on the attached table (in our example, the emp table). As Oracle iterates over each row in the result set, it loads the original values of the row into the both OLD and NEW records, and then applies the statement action (SET sal=sal*1.1) to the NEW record.
When the trigger executes, it can access both the old and new values through
these records. From the example above:
When the statement is applied to the database, values are copied from the NEW record to the row in the table. Now recall that a trigger could occur BEFORE or AFTER the triggering statement. In the case of row triggers, both the before and after triggers can reference the OLD and NEW values. In addition, the before row trigger can assign values to the NEW record.
Again, when the statement is applied to the database, values are copied from the NEW record to the row in the table. So you can directly assign new values using the NEW record. When using NEW and OLD records keep in mind:
Do NOT modify values like this:
This misses the point of using NEW and OLD. It also raises the specter of recursive triggers. Recursive triggersIf a trigger contains a DML statement, it may cause another trigger fire, which may cause another trigger to fire, and so on... This is called cascading triggers, and it should be avoided because it becomes very difficult to debug. The simplest form of a cascading trigger is a recursive trigger; recursive triggers usually fail. Recursive triggers contain a DML statement in the body which causes the trigger to fire again. Here is a simple recursive trigger.
The problem here is that when the EMP table is updated, it will fire this trigger which will update the EMP table, which will fire this trigger, which will update the emp table, ... and so on until Oracle runs out of memory. (Usually the trigger will fire 50 times before failing; the actual number depends on parameters set by the DBA.)
WHEN clauseTriggers are often designed to fire only when the row contains specific values. In our previous example, the trigger body do nothing unless the new job title is SALESMAN.
Oracle provides a more efficient way to accomplish this when using row triggers: the WHEN clause. The WHEN clause contains a condition which is evaluated for each row. The trigger is fired is fired only when the condition is true.
Note that references to OLD and NEW in the WHEN clause are not prefixed with ':'. REFERENCING clauseTriggers can access the old and new values of a row through the records named OLD and NEW. You can specify alternate names using the REFERENCING clause. (This sometimes improves clarity of the code. It is rarely necessary.)
Mutating tablesDML statements are supposed to guarantee atomicty. That is, the statement executes all the way, or not at all, and there is no in-between state. Since row triggers are fired for each affected row, row level triggers have the unique ability to look "inside" a statement, to the state of the data after the statement has been issued but before the statement has completed. If that ability were not restricted in some way, it would violate the atomicty of the statement, because at any point we might be seeing the data partway through the statement. To ensure atomicity, row level triggers can see the old and new values for a particular row, but they can only see the values in that row. A row level triggers cannot read from its attached table with a select statement because it would violate atomicity.
If you attempt to read from the attached table, you will receive a mutating table error message. This indicates that your trigger is not designed correctly. Unfortunately, Oracle will not catch this error at compile time. The error will appear at run time and will prevent the associated DML statement from succeeding. To illustrate the problem of mutating tables, let's say we want to track the highest salary for each department. We'll add a column to the DEPT table to track this. This means that the table EMP will require a trigger to fire each time an employee is inserted, deleted, or a salary is updated. Each time the trigger fired, it should update the salaries for all affected departments. Your first approach might look something like this:
This trigger compiles without error. But when it's invoked, I get the following error message:
This limitation cannot be completely overcome, but it's impact can be mitigated through the use of statement level triggers. Statement level triggers execute before or after the statement and so statement triggers are never vulnerable to mutating table error. Therefore, you could use a less efficient approach and re-calculate high salaries for all departments like so:
This trigger compiles. When invoked, it correctly populates the new column. SQL> DELETE FROM emp WHERE empno=1000; 1 row deleted. This approach works for the emp table because there are few departments and few records. However the performance would degrade quickly as we added employees and department numbers. A faster but more more complex approach is outlined below:
While it's certainly more complex, this is an efficient approach to this problem. Uses of triggersEnforcing business rulesThe declarative constraints should be used as often as possible, but the declarative constraints can only accommodate relative simple business logic. Handling your complex business logic with triggers ensures you won't leave your data integrity to your application design alone.
Deriving values and de-normalizationOccasionally triggers are used to derive the value of one column from others. This trigger calculates the highest salary in each department and modifies data in the dept table accordingly.
Deriving and storing values is sometimes known as de-normalization because you end up representing the same data twice in the database. I'm not a purist on de-normalization. Because the RDBMS of today are not as fast or well organized as database developers would like, sometimes de-normalization a reasonable compromise. But it is a compromise: whatever you gain in speed or simplicity, you often end up losing somewhere else in the data model or application architecture. In the example above performance quickly degrades as the number of rows in the EMP table increases. And in the optimized solution described above (in the Mutating tables section above), performance is robust; but with the addition of three triggers and a package specification, the architecture becomes much more complex. Another use of derived values is the enforcement of default column values. Consider that a default column value is used when a column is omitted from the insert list. But a user could always specify a new value, overriding the default. Sometimes, you want to data to be derived regardless of what the user specifies. The following example adds a timestamp field in the EMP table and attaches a trigger which records when each record was last updated.
Now the trigger is overriding any user supplied values. Even if we supply a value for UPDATE_TS, the trigger will override it.
AuditingOracle has some built in auditing functionality. However, Oracle's auditing records only information at the statement level and cannot access the affected row values. You can supplement Oracle's auditing using row triggers.
PrivilegesTriggers execute with the privilege of the owner (the definer). You do not grant rights on triggers, only on the actions which cause the triggers to fire. Therefore if you have the privilege to INSERT into a table, you have the right to fire all INSERT triggers on that table. Managing triggersYou can list all package specifications you own with the data dictionary view USER_OBJECTS: SELECT object_name, object_type, created, timestamp, status FROM user_objects WHERE object_type='TRIGGER';OBJECT_NAME OBJECT_TYPE CREATED TIMESTAMP STATUS ------------------------------ ------------------ --------- ------------------- ------- DERIVE_COMM_TR TRIGGER 09-JUL-01 2001-07-09:23:07:53 VALID HI_SAL_TR TRIGGER 09-JUL-01 2001-07-09:23:07:53 VALID TRIGGER_TIMING_AR TRIGGER 09-JUL-01 2001-07-09:23:29:47 VALID TRIGGER_TIMING_AS TRIGGER 09-JUL-01 2001-07-09:23:29:46 VALID TRIGGER_TIMING_BR TRIGGER 09-JUL-01 2001-07-09:23:29:46 VALID TRIGGER_TIMING_BS TRIGGER 09-JUL-01 2001-07-09:23:29:46 VALID UPDATE_EMP_TR TRIGGER 09-JUL-01 2001-07-09:23:02:44 VALID Curiously, the source code for a trigger is not in USER_SOURCE but instead in USER_TRIGGERS. User triggers contains basically all the information about a specific trigger. DESC user_triggersName Null? Type ----------------------------------------- -------- -------------- TRIGGER_NAME VARCHAR2(30) TRIGGER_TYPE VARCHAR2(16) TRIGGERING_EVENT VARCHAR2(216) TABLE_OWNER VARCHAR2(30) BASE_OBJECT_TYPE VARCHAR2(16) TABLE_NAME VARCHAR2(30) COLUMN_NAME VARCHAR2(4000) REFERENCING_NAMES VARCHAR2(128) WHEN_CLAUSE VARCHAR2(4000) STATUS VARCHAR2(8) DESCRIPTION VARCHAR2(4000) ACTION_TYPE VARCHAR2(11) TRIGGER_BODY LONG To get the source code for a trigger: SQL> SHOW LONG long 80 SQL> SET LONG 1000 SQL> SELECT trigger_body FROM user_triggers WHERE trigger_name='DERIVE_COMM_TR'; | |||||||||||||||||||||||||||||