I have a table with the following structure:
create table treballa (
code varchar2(4),
name varchar2(20),
director varchar2(4),
department number,
salary int,
primary key (code),
foreign key (director) references treballa(code)
)
And I need to create 1 trigger that checks if the department number of the updated worker, the total salary between all the workers of that department doesn't exceeds 10000, but I don't know how to do it really... Could you please help me? Thank you very much.
EDIT:
CREATE OR REPLACE TRIGGER controlsalaridept BEFORE INSERT OR UPDATE ON TREBALLA
FOR EACH ROW
DECLARE
salaritotal INT := 0;
BEGIN
IF INSERTING THEN
SELECT sum(salary) INTO salaritotal FROM TREBALLA WHERE DEPARTMENT LIKE :new.DEPARTAMENT;
DBMS_OUTPUT.PUT_LINE('Salari Total abans suma:'||salaritotal);
salaritotal := salaritotal + :new.SALARY;
DBMS_OUTPUT.PUT_LINE('Salari Total després:'||salaritotal);
IF salaritotal > 10000 THEN
raise_application_error(-20025, 'La suma del salari total de cada departament supera els 10000 Euros');
END IF;
END IF;
IF UPDATING THEN
SELECT sum(salary) INTO salaritotal FROM TREBALLA WHERE DEPARTMENT LIKE :old.DEPARTAMENT;
DBMS_OUTPUT.PUT_LINE('Salari Total abans suma:'||salaritotal);
salaritotal := salaritotal - :old.SALARY + :new.SALARY;
DBMS_OUTPUT.PUT_LINE('Salari Total després:'||salaritotal);
IF salaritotal > 10000 THEN
raise_application_error(-20026,'La suma del salari total de cada departament supera els 10000 Euros');
END IF;
END IF;
END;
END EDIT/
ERROR WHEN UPDATING:
[42000][4091] ORA-04091: table SPECIAL.TREBALLA is mutating,
trigger/function may not see it ORA-06512: at
"SPECIAL.CONTROLSALARIDEPT", line 14 ORA-04088: error during execution
of trigger 'SPECIAL.CONTROLSALARIDEPT'
PD: Sorry, I'm very new on oracle and I need help with this trigger, I don't know even if it's right what I'm doing.... The first part of the trigger "IF INSERTING" works well, the problem is with the UPDATING...
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:
- The update statament is fired:
UPDATE treballa SET salary = salary + 10
- The declarative section of the trigger is executed, that is:
Departments
variable is initialized
BEFORE 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.