插入或删除后的 Oracle 触发器

Posted

技术标签:

【中文标题】插入或删除后的 Oracle 触发器【英文标题】:Oracle trigger after insert or delete 【发布时间】:2015-06-11 22:52:38 【问题描述】:

对不起我的英语。

我有 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'

我做错了什么?

【问题讨论】:

您正尝试在您正在 DELETE 一行的表上运行 SELECT 语句。 Google 错误代码 ORA-04091,你会得到很多答案。 【参考方案1】:

您遇到的是经典的“变异表”异常。在 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 ROWAFTER STATEMENT)。请注意,时间点总是按给定的顺序调用。当执行适当的 SQL 语句(即 INSERT INTO TABLE1DELETE FROM TABLE1)并触发此触发器时,要调用的第一个时间点将是 BEFORE STATEMENTBEFORE STATEMENT 处理程序中的代码将分配一个 PL/SQL存放一堆数字的表。在这种情况下,要存储在 PL/SQL 表中的数字将是来自 TABLE1 的 TABLE2_ID 值。 (例如,使用 PL/SQL 表而不是数组,因为表可以保存不同数量的值,而如果我们使用数组,我们必须提前知道需要存储多少个数字。我们无法提前知道特定语句会影响多少行,因此我们使用 PL/SQL 表)。

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

Documentation here.

【讨论】:

【参考方案2】:

您必须为删除定义一个前触发器。尝试使用两个触发器

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;

【讨论】:

【参考方案3】:

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

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

【讨论】:

这似乎更适合作为对现有答案的回复,而不是全新的答案。

以上是关于插入或删除后的 Oracle 触发器的主要内容,如果未能解决你的问题,请参考以下文章

ORACLE 和触发器(插入、更新、删除)

Oracle中的触发器是请求的一部分吗?

Oracle 触发器在插入或更新时更新字段

oracle 中如何删除或修改 触发器?

在 oracle 中创建一个触发器,它不允许使用 if 条件进行任何插入或更新

oracle 触发器插入和更新时,怎么操作