I have a table with the following structure:
create table treballa (
code varchar2(4),
name varchar2(20),
director varchar2(4),
department number,
salary
Try a compound trigger:
CREATE OR REPLACE TRIGGER compound_trigger_name
FOR INSERT OR UPDATE OF salary ON treballa
COMPOUND TRIGGER
TYPE Departments_t IS TABLE OF treballa.department%TYPE INDEX BY varchar2(100);
Departments Departments_t;
BEFORE EACH ROW IS
BEGIN
-- collect updated or inserted departments
Departments( :new.department ) := :new.department;
END BEFORE EACH ROW;
AFTER STATEMENT IS
sum_sal NUMBER;
BEGIN
-- for each updated department check the restriction
FOR dept IN Departments.FIRST .. Departments.LAST
LOOP
SELECT sum(salary) INTO sum_sal FROM treballa WHERE department = dept;
IF sum_sal > 1000 THEN
raise_application_error(-20123, 'The total salary for department '||dept||' cannot exceed 1000');
END IF;
END LOOP;
END AFTER STATEMENT;
END compound_trigger_name;
/
========EDIT - a few question and answers ===========
Q: Why a mutating table error occurs ?
A: This is described in the documentation:
http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/triggers.htm#g1699708
Trigger Restrictions on Mutating Tables
A mutating table is a table that is being modified by an UPDATE, DELETE, or INSERT statement, or a table that might be updated by the effects of a DELETE CASCADE constraint.The session that issued the triggering statement cannot query or modify a mutating table. This restriction prevents a trigger from seeing an inconsistent set of data.
This restriction applies to all triggers that use the FOR EACH ROW clause. Views being modified in INSTEAD OF triggers are not considered mutating.
When a trigger encounters a mutating table, a run-time error occurs, the effects of the trigger body and triggering statement are rolled back, and control is returned to the user or application. (You can use compound triggers to avoid the mutating-table error. For more information, see Using Compound Triggers to Avoid Mutating-Table Error.)
Q: how to avoid a mutating table error ?
A: The documentation recommends the use of a coumpound trigger, see this: http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/triggers.htm#CHDFEBFJ
Using Compound Triggers to Avoid Mutating-Table Error You can use compound triggers to avoid the mutating-table error (ORA-04091) described in Trigger Restrictions on Mutating Tables.
Q: What is a compound trigger and how does it work ?
A: This is a huge topic, please refer to the documentation here: http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/triggers.htm#CIHEFGFD
In short: this is a special kind of a trigger that makes psiible to combine four types of separate triggers: BEFORE statement
, BEFORE-for each row
, AFTER for each row
and AFTER statament
into a one declaration. It makes it easier to implement some scenarious in which there is a need to pass some data from one trigger to another one. Please study the above link for more details.
Q: But what actually does "Departments( :new.department ) := :new.department;
?
A: This declaration stores a department number into an associative array.
This array is declared in a declarative part of the compound trigger:
TYPE Departments_t IS TABLE OF treballa.department%TYPE INDEX BY varchar2(100);
Departments Departments_t;
The documentation related to the compound triggers says that: http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/triggers.htm#CIHJBEFE
The optional declarative part (the first part) declares variables and subprograms that timing-point sections can use. When the trigger fires, the declarative part executes before any timing-point sections execute. Variables and subprograms declared in this section have firing-statement duration.
The above means that Departments
variable is initialized only once at the beginning of the whole processing, just after the trigger fires. "Firing-statement duration" means that this variable is destroyed after the trigger finishes.
This statement: Departments( :new.department ) := :new.department;
stores a department number in the associative array. It is in BEFORE EACH ROW
section, then it is executed for each row that is updated (or inserted) by the update/insert statement.
:new
and :old
are pseudorecords, more on them you can find here: http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/triggers.htm#LNPLS99955
In short: :new.department
retrieves a new value of department
column- for a currently updated row (updated value - AFTER the update), while :old.department
gives an old value of this column (BEFORE the update).
This collection is later used in the AFTER STATEMENT
, when the trigers pick all updated departments (in a FOR-LOOP), for each departmens fires SELECT SUM(salary) ...
and then checks if this sum is less than 1000
Consider a simple update: UPDATE treballa SET salary = salary + 10
. This is a single update statement, but changes many rows at once. The order of execution of our trigger is as follows:
UPDATE treballa SET salary = salary + 10
Departments
variable is initializedBEFORE EACH ROW
section is executed, separately for each updated row - as many times as there are rows to be updated. In this place we collect all departments from changed rows.AFTER STATEMENT
section is executed. At this point the table is already updated - all rows already have new, updated salaries. We loop throught departments saved in Departments
and for each one we check if sum of salaries is less or equal to 1000. If this sum is > 1000 for any of these departments, then an error is thrown, and the whole update is aborted and rolled back. Otherwise the trigger finishes, and the update is done (but you need to commit these changes anyways). Q: What is an associative array, and why just this kind of collection is used, rather than other collections (a varray or a nested table) ?
A: PL/SQL collections are a huge topic. Follow this link to learn them: http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/composites.htm#LNPLS005
In short - Associative array (or index-by table) is like a map in java (hashmap, treemap etc) - it is a set of key-value pairs, and each key is unique. You can put the same key many times to this array (with different values), but this key will be stored only once - it is unique.
I've used it to get unique set of departments.
Consider our update example again: UPDATE treballa SET salary = salary + 10
- this command touches hundreds of rows that have the same department. I don't want a collection with the same department duplicated 100 times, I need an unique set of departments, and I want to execute our query SELECT sum()...
only once for each department, not 100 times. With the help of the sssociative array it is done automatically - I get unique set of deparments.