如何使用触发器在同一张表中插入新行(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触发器,几张表有关联,修改其中一张主表中的某一个字段,其他关联表中的该字段也跟着联动修改