插入或删除后的 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 STATEMENT
、BEFORE ROW
、AFTER ROW
和 AFTER STATEMENT
)。请注意,时间点总是按照给定的顺序调用。当执行适当的 SQL 语句(即 INSERT INTO TABLE1
或 DELETE 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 找到适当的更新。
您必须使用两个触发器
为 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;
...
对不起我的英语。
我有 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 STATEMENT
、BEFORE ROW
、AFTER ROW
和 AFTER STATEMENT
)。请注意,时间点总是按照给定的顺序调用。当执行适当的 SQL 语句(即 INSERT INTO TABLE1
或 DELETE 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 找到适当的更新。
您必须使用两个触发器
为 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;
...