插入或删除后的 Oracle 触发器

Oracle trigger after insert or delete

对不起我的英语。

我有 2 个表:

Table1
id
table2_id
num
modification_date 

Table2
id
table2num

我想制作一个触发器,在 Table1 中插入或删除后更新 Table2.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'

我做错了什么?

您 运行 进入的是经典 "mutating table" 异常。在 ROW 触发器中,Oracle 不允许您 运行 针对定义触发器的 table 的查询 - 所以它是 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 ROWAFTER STATEMENT)。请注意,时间点总是按照给定的顺序调用。当执行适当的 SQL 语句(即 INSERT INTO TABLE1DELETE FROM TABLE1)并触发此触发器时,第一个要调用的时间点将是 BEFORE STATEMENT,并且BEFORE STATEMENT 处理程序将分配一个 PL/SQL table 来保存一堆数字。在这种情况下,要存储在 PL/SQL table 中的数字将是来自 TABLE1 的 TABLE2_ID 值。 (例如,使用 PL/SQL table 而不是数组,因为 table 可以保存不同数量的值,而如果我们使用数组,我们必须知道提前我们需要存储多少数字。我们无法提前知道有多少行将受到特定语句的影响,因此我们使用 PL/SQL table).

当到达 AFTER EACH ROW 时间点并且我们发现正在处理的语句是 INSERT 时,触发器会继续执行对 TABLE2 的必要更新,因为这不会导致问题。但是,如果正在执行 DELETE,触发器会将 TABLE1.TABLE2_ID 保存到之前分配的 PL/SQL table 中。当最终到达 AFTER STATEMENT 时间点时,迭代先前分配的 PL/SQL table,并为每个 TABLE2_ID 找到适当的更新。

Documentation here.

您必须使用两个触发器

为 delete.Try 定义前触发器
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;

@psaraj12 答案是最好的恕我直言,但在 DELETE 触发器中我会使用 :OLD 表示法,因为内部查询是不必要的并且会显着减慢触发速度:

... 
    BEFORE DELETE ON table1
    FOR EACH ROW
        UPDATE table2
        SET    table2num = :OLD.num
        WHERE  table2.id = :OLD.table2_id;
...