如何使用触发器在同一张表中插入新行(Oracle PL/SQL 10G)?

Posted

技术标签:

【中文标题】如何使用触发器在同一张表中插入新行(Oracle PL/SQL 10G)?【英文标题】:How to use trigger to insert a new row in same table (Oracle PL/SQL 10G)? 【发布时间】:2017-09-08 05:49:56 【问题描述】:

我的表结构是这样的:

ID   NAME   DESG     START_DT     END_DT      CURRENT_FLAG
101  John   Trainee  01-Jan-2016  01-08-2017  Yes

现在我想更新这条记录(更改名称),同时他的旧历史记录应该作为新行插入。有点像这样:

ID   NAME   DESG       START_DT      END_DT       CURRENT_FLAG
101  John   Trainee    01-01-2016   01-08-2017    No
102  John   Associate  02-08-2016   01-01-2099    Yes

我尝试使用带有以下代码的触发器,但这返回了一些错误。

代码

CREATE or REPLACE TRIGGER TRI1
BEFORE UPDATE OF type ON TB818 
FOR EACH ROW
BEGIN 
INSERT INTO TB818
VALUES 
(:old.id,
 :old.name, 
 :old.desg, 
 :old.start_dt,
 :old.end_dt,
 :old.current_flag);
END;

----------------------------

> Update TB818 Set Desg='G' where ID=1   

错误:

ORA-04091: table SYSTEM.TB818 is mutating, trigger/function may not see it
ORA-06512: at "SYSTEM.TRI1", line 2
ORA-04088: error during execution of trigger 'SYSTEM.TRI1'
1. Update TB818 Set type='G' where Id=1

我怎样才能做到这一点?

【问题讨论】:

什么是类型?没有名为 type 的列吗? 哦……我打错了。这是 Desg='G' 尝试改正后再执行 简而言之 - 你不能这样做。将历史记录存储在单独的表TB818_History 中。您避免了变异表错误,而且 - 最重要的是 - 您的数据模型将清晰易懂,并且当某些人忘记在更新查询中添加 AND CURRENT_FLAG ='Yes' 子句时,您将避免数据损坏。 还有其他选择吗?是的 - 使用复合触发器,有一个example in the documentation 怎么做。另一个“解决方案”是使用自主触发器,但这是一个“hack”而不是解决方案,因为它总是插入一行,而不管主事务提交或回滚 - 它独立于调用事务。 【参考方案1】:

我建议您使用单独的表来存储历史记录。 您避免了变异表错误,并且 - 最重要的是 - 您的数据模型将清晰易懂,并且当某些人忘记在更新查询中添加 AND CURRENT_FLAG ='Yes' 子句时,您将避免数据损坏。 如果必须使用同一个表,则可以使用复合触发器。 这是一个示例 - 假设 ID 是标识列或使用某些序列 + 触发器自动生成。


样本数据:

CREATE TABLE TB818(
    ID  NUMBER GENERATED ALWAYS AS IDENTITY,
    NAME  varchar2(10),
    DESG   varchar2(10),  
    START_DT   date,  
    END_DT date,     
    CURRENT_FLAG varchar2(3) DEFAULT 'Yes' check( CURRENT_FLAG IN ('Yes','No')) 
)
;

INSERT INTO TB818(  NAME ,DESG,START_DT,END_DT ) VALUES('John', 'Trainee', sysdate, sysdate+20);
INSERT INTO TB818(  NAME ,DESG,START_DT,END_DT ) VALUES('Mark', 'Associate', sysdate, sysdate+20);

commit;

触发器:

set define off;
CREATE OR REPLACE TRIGGER TRI1
FOR UPDATE OF DESG ON TB818 
COMPOUND TRIGGER

   TYPE TB818_ROW_T IS TABLE OF TB818%ROWTYPE;
        TB818_ROWS TB818_ROW_T;
    BEFORE STATEMENT IS
    BEGIN
       TB818_ROWS := TB818_ROW_T();
    END BEFORE STATEMENT;

    BEFORE EACH ROW IS
       TB818_ROW TB818%ROWTYPE;
    BEGIN
       TB818_ROW.NAME := :old.name;
       TB818_ROW.DESG := :old.DESG;
       TB818_ROW.START_DT := :old.START_DT;
       TB818_ROW.END_DT := :old.END_DT;
       TB818_ROWS.EXTEND;
       TB818_ROWS( TB818_ROWS.last ) := TB818_ROW;
    END BEFORE EACH ROW;

    AFTER STATEMENT IS
    BEGIN
       FORALL x IN TB818_ROWS.First .. TB818_ROWS.Last
       INSERT INTO TB818(  NAME ,DESG,START_DT,END_DT, CURRENT_FLAG )
              VALUES( TB818_ROWS( x ).name, TB818_ROWS( x ).desg,
                      TB818_ROWS( x ).start_dt, TB818_ROWS( x ).end_dt,
                      'No'
              );
    END AFTER STATEMENT;
END;
/

测试用例显示如果某人不知道历史记录行存储在同一个表中并且不知道他必须始终追加这个“功能”可能会发生什么@ 987654324@ 对所有 UPDATE 查询 - 经过数百次更新后系统变慢,然后会出现“内存不足”错误,最终表空间将满,系统将崩溃。 这不是发布这些更新的人的错 - 这是一个糟糕的、不直观的设计,会误导人们并使系统变得敏感。

SELECT * FROM TB818;

        ID NAME       DESG       START_DT         END_DT           CUR
---------- ---------- ---------- ---------------- ---------------- ---
         1 John       Trainee    2017/09/08 09:21 2017/09/28 09:21 Yes
         2 Mark       Associate  2017/09/08 09:21 2017/09/28 09:21 Yes

UPDATE TB818 SET DESG = 'XXX';
SELECT * FROM TB818;

        ID NAME       DESG       START_DT         END_DT           CUR
---------- ---------- ---------- ---------------- ---------------- ---
         1 John       XXX        2017/09/08 09:21 2017/09/28 09:21 Yes
         2 Mark       XXX        2017/09/08 09:21 2017/09/28 09:21 Yes
         3 John       Trainee    2017/09/08 09:21 2017/09/28 09:21 No 
         4 Mark       Associate  2017/09/08 09:21 2017/09/28 09:21 No 

UPDATE TB818 SET DESG = 'ZZZ';
SELECT * FROM TB818;

        ID NAME       DESG       START_DT         END_DT           CUR
---------- ---------- ---------- ---------------- ---------------- ---
         1 John       ZZZ        2017/09/08 09:21 2017/09/28 09:21 Yes
         2 Mark       ZZZ        2017/09/08 09:21 2017/09/28 09:21 Yes
         3 John       ZZZ        2017/09/08 09:21 2017/09/28 09:21 No 
         4 Mark       ZZZ        2017/09/08 09:21 2017/09/28 09:21 No 
         5 John       XXX        2017/09/08 09:21 2017/09/28 09:21 No 
         6 Mark       XXX        2017/09/08 09:21 2017/09/28 09:21 No 
         7 John       Trainee    2017/09/08 09:21 2017/09/28 09:21 No 
         8 Mark       Associate  2017/09/08 09:21 2017/09/28 09:21 No 

UPDATE TB818 SET DESG = 'ABCD';
SELECT * FROM TB818;

        ID NAME       DESG       START_DT         END_DT           CUR
---------- ---------- ---------- ---------------- ---------------- ---
         1 John       ABCD       2017/09/08 09:21 2017/09/28 09:21 Yes
         2 Mark       ABCD       2017/09/08 09:21 2017/09/28 09:21 Yes
         3 John       ABCD       2017/09/08 09:21 2017/09/28 09:21 No 
         4 Mark       ABCD       2017/09/08 09:21 2017/09/28 09:21 No 
         5 John       ABCD       2017/09/08 09:21 2017/09/28 09:21 No 
         6 Mark       ABCD       2017/09/08 09:21 2017/09/28 09:21 No 
         7 John       ABCD       2017/09/08 09:21 2017/09/28 09:21 No 
         8 Mark       ABCD       2017/09/08 09:21 2017/09/28 09:21 No 
         9 John       ZZZ        2017/09/08 09:21 2017/09/28 09:21 No 
        10 Mark       ZZZ        2017/09/08 09:21 2017/09/28 09:21 No 
        11 John       ZZZ        2017/09/08 09:21 2017/09/28 09:21 No 
        12 Mark       ZZZ        2017/09/08 09:21 2017/09/28 09:21 No 
        13 John       XXX        2017/09/08 09:21 2017/09/28 09:21 No 
        14 Mark       XXX        2017/09/08 09:21 2017/09/28 09:21 No 
        15 John       Trainee    2017/09/08 09:21 2017/09/28 09:21 No 
        16 Mark       Associate  2017/09/08 09:21 2017/09/28 09:21 No 

UPDATE TB818 SET DESG = 'QWERTY';
SELECT count(*) FROM TB818;
  COUNT(*)
----------
        32


UPDATE TB818 SET DESG = 'wwww';
SELECT count(*) FROM TB818;

  COUNT(*)
----------
        64

【讨论】:

以上是关于如何使用触发器在同一张表中插入新行(Oracle PL/SQL 10G)?的主要内容,如果未能解决你的问题,请参考以下文章

触发器处理表更新

想写一个DB2触发器,几张表有关联,修改其中一张主表中的某一个字段,其他关联表中的该字段也跟着联动修改

根据同一张表中的其他数据验证插入的数据(Oracle)

如何在 SQL 中对触发器进行语法化,以便在插入后从同一个表中更新列(oracle 数据库)

在oracle中怎么把一张表的数据插入到另一张表中

在插入新行之前删除行[重复]