2015-04-07 64 views
3

对不起,我的英语。插入或删除后的Oracle触发器

我有2个表:

Table1 
id 
table2_id 
num 
modification_date 

Table2 
id 
table2num 

我想打一个触发器,它后插入或删除Table1Table2.table1lastnum更新的最后一个值num

我的触发器:

CREATE OR REPLACE TRIGGER TABLE1_NUM_TRG 
    AFTER INSERT OR DELETE ON table1 
    FOR EACH ROW 
BEGIN 
    IF INSERTING then 

    UPDATE table2 
    SET table2num = :new.num 
    WHERE table2.id = :new.table2_id; 

    ELSE 

    UPDATE table2 
    SET table2num = (SELECT num FROM (SELECT num FROM table1 WHERE table2_id = :old.table2_id ORDER BY modification_date DESC) WHERE ROWNUM <= 1) 
    WHERE table2.id = :old.table2_id; 

    END IF; 

END TABLE1_NUM_TRG; 

但删除后Table1我有错误:

ORA-04091: table BD.TABLE1 is mutating, trigger/function may not see it 
ORA-06512: at "BD.TABLE1_NUM_TRG", line 11 
ORA-04088: error during execution of trigger 'BD.TABLE1_NUM_TRG' 

我在做什么错?

+1

你试图运行从你'DELETE'ing一排桌子的'SELECT'声明触发之前定义为delete.Try。 –

+0

谷歌的错误代码ORA-04091,你会得到很多答案。 –

回答

5

你碰到的是经典的“变异表”异常。在ROW触发器中,Oracle不允许您针对定义触发器的表运行查询 - 因此它是针对导致此问题的触发器的DELETING部分中的TABLE1的SELECT

有几种方法可以解决这个问题。也许在这种情况下,最好是使用复合触发器,它看起来是这样的:

CREATE OR REPLACE TRIGGER TABLE1_NUM_TRG 
    FOR INSERT OR DELETE ON TABLE1 
COMPOUND TRIGGER 
    TYPE NUMBER_TABLE IS TABLE OF NUMBER; 
    tblTABLE2_IDS NUMBER_TABLE; 

    BEFORE STATEMENT IS 
    BEGIN 
    tblTABLE2_IDS := NUMBER_TABLE(); 
    END BEFORE STATEMENT; 

    AFTER EACH ROW IS 
    BEGIN 
    IF INSERTING THEN 
     UPDATE TABLE2 t2 
     SET t2.TABLE2NUM = :new.NUM 
     WHERE t2.ID = :new.TABLE2_ID; 
    ELSIF DELETING THEN 
     tblTABLE2_IDS.EXTEND; 
     tblTABLE2_IDS(tblTABLE2_IDS.LAST) := :new.TABLE2_ID; 
    END IF; 
    END AFTER EACH ROW; 

    AFTER STATEMENT IS 
    BEGIN 
    IF tblTABLE2_IDS.COUNT > 0 THEN 
     FOR i IN tblTABLE2_IDS.FIRST..tblTABLE2_IDS.LAST LOOP 
     UPDATE TABLE2 t2 
      SET t2.TABLE2NUM = (SELECT NUM 
           FROM (SELECT t1.NUM 
             FROM TABLE1 t1 
             WHERE t1.TABLE2_ID = tblTABLE2_IDS(i) 
             ORDER BY modification_date DESC) 
           WHERE ROWNUM = 1) 
      WHERE t2.ID = tblTABLE2_IDS(i); 
     END LOOP; 
    END IF; 
    END AFTER STATEMENT; 
END TABLE1_NUM_TRG; 

复合触发器允许每个时间点(BEFORE STATEMENTBEFORE ROWAFTER ROW,并且AFTER STATEMENT)进行处理。请注意,时间点总是按给定的顺序调用。当执行适当的SQL语句(即INSERT INTO TABLE1DELETE FROM TABLE1)并触发此触发器时,要调用的第一个时间点将为BEFORE STATEMENT,并且BEFORE STATEMENT处理程序中的代码将分配PL/SQL表以容纳一堆数字。在这种情况下,要存储在PL/SQL表中的数字将是TABLE1的TABLE2_ID值。 (使用PL/SQL表来代替数组,因为一个表可以容纳不同数量的值,而如果我们使用了一个数组,我们必须事先知道需要存储多少个数。我们无法预先知道特定语句会影响多少行,因此我们使用PL/SQL表)。当到达AFTER EACH ROW时间点,并且我们发现正在处理的语句是INSERT时,触发器会继续执行并对TABLE2执行必要的UPDATE,因为这不会导致问题。但是,如果正在执行DELETE,触发器会将TABLE1.TABLE2_ID保存到先前分配的PL/SQL表中。当最终到达AFTER STATEMENT时间点时,先前分配的PL/SQL表被遍历,并且对于每个TABLE2_ID找到适当的更新。

Documentation here

分享和享受。

1

你必须使用两个触发器

CREATE OR REPLACE TRIGGER INS_TABLE1_NUM_TRG 
AFTER INSERT ON table1 
FOR EACH ROW 
BEGIN 
    UPDATE table2 
    SET table2num = :new.num 
    WHERE table2.id = :new.table2_id; 
END INS_TABLE1_NUM_TRG; 


CREATE OR REPLACE TRIGGER DEL_TABLE1_NUM_TRG 
BEFORE DELETE ON table1 
FOR EACH ROW 
BEGIN 
    UPDATE table2 
    SET table2num = (SELECT num FROM 
    (SELECT num FROM table1 WHERE table2_id = :old.table2_id 
    ORDER BY modification_date DESC) 
    WHERE ROWNUM <= 1) 
    WHERE table2.id = :old.table2_id; 
END DEL_TABLE1_NUM_TRG;