Update Trigger PL/SQL Oracle

后端 未结 1 1443
清歌不尽
清歌不尽 2021-01-27 16:10

I have a table with the following structure:

create table treballa (
code varchar2(4), 
 name varchar2(20), 
 director varchar2(4), 
department number, 
salary          


        
1条回答
  •  陌清茗
    陌清茗 (楼主)
    2021-01-27 16:50

    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:

    1. The update statament is fired: UPDATE treballa SET salary = salary + 10
    2. The declarative section of the trigger is executed, that is: Departments variable is initialized
    3. 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.
    4. 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.

    0 讨论(0)
提交回复
热议问题