在 Oracle 插入触发器期间更新以前的记录版本
Posted
技术标签:
【中文标题】在 Oracle 插入触发器期间更新以前的记录版本【英文标题】:Update previous record version during Oracle insert trigger 【发布时间】:2016-10-05 14:24:05 【问题描述】:这是我在 Table_A 上将参数存储到系统的插入触发器。当我插入表时,我想更改最后一条记录的 end_date 以保持记录版本控制。
create or replace trigger parameter_version
before insert
on parameters
for each row
declare
v_is_exist number := 0;
v_rowid rowid;
begin
select count(*) into v_is_exist from parameters where name = :new.name; -- check if parameter exist
select rowid into v_rowid from parameters where name = :new.name and end_date is null; -- record rowid, which sholud be changed
if v_is_exist <> 0 then
set end_date = :new.start_date - 1
end if;
end;
插入前表中的情况是:
| id | name | value | start_date | end_date |
-----------------------------------------------
| 1 |Par_A | 10 | 2016-09-01 | 2016-10-01 |
-----------------------------------------------
| 2 |Par_A | 20 | 2016-10-02 | 2016-10-03 |
-----------------------------------------------
| 3 |Par_A | 30 | 2016-10-05 | <null> |
-----------------------------------------------
id=3 的记录应将 end_date 设置为 :new_start_date - 1(关闭版本),并且在插入记录时,我有一个 start_date = sysdate 的下一个参数版本。
我遇到了 ORA-04091 错误“表名正在发生变化,触发器/函数可能看不到它”。
我知道这个案子很难,而且可能不可能,但也许有人知道解决方案? 或者可能存在另一种解决方案?
【问题讨论】:
这不是使用触发器的好选择。正如错误消息所暗示的那样,表的内容仍在不断变化,并且在更新所有相关行后触发器可能不会触发。让调用代码进行必要的更新要好得多。如果这是一个严格的要求,最好研究一下Compound Triggers 取决于您的 Oracle 数据库版本,请查看compound triggers(仅作为最后的手段,我认为您应该修改您的设计) 您需要使用语句级触发器。行级触发器无法修改正在更新的表。 【参考方案1】:您可以使用带有 LEAD 分析函数的 After Statement 触发器来处理此问题:
DROP TABLE demo;
CREATE TABLE demo( id NUMBER
, name VARCHAR2( 30 )
, VALUE NUMBER
, start_date DATE
, end_date DATE
);
INSERT INTO demo( id, name, VALUE, start_date, end_date )
VALUES ( 1, 'Par_A', 10, TO_DATE( '2016-09-01', 'YYYY-MM-DD' ), TO_DATE( '2016-10-01', 'YYYY-MM-DD' ) );
INSERT INTO demo( id, name, VALUE, start_date, end_date )
VALUES ( 2, 'Par_A', 20, TO_DATE( '2016-10-02', 'YYYY-MM-DD' ), TO_DATE( '2016-10-04', 'YYYY-MM-DD' ) );
INSERT INTO demo( id, name, VALUE, start_date )
VALUES ( 3, 'Par_A', 30, TO_DATE( '2016-10-05', 'YYYY-MM-DD' ) );
INSERT INTO demo( id, name, VALUE, start_date )
VALUES ( 4, 'Par_A', 40, TO_DATE( '2016-10-07', 'YYYY-MM-DD' ) );
INSERT INTO demo( id, name, VALUE, start_date )
VALUES ( 5, 'Par_A', 50, TO_DATE( '2016-10-11', 'YYYY-MM-DD' ) );
COMMIT;
SELECT id
, name
, start_date
, end_date
, LEAD( start_date ) OVER( PARTITION BY name ORDER BY start_date ) - 1 AS new_date
FROM demo
WHERE end_date IS NULL
ORDER BY id;
CREATE OR REPLACE TRIGGER demo_aius
AFTER INSERT OR UPDATE
ON demo
REFERENCING NEW AS new OLD AS old
DECLARE
CURSOR c_todo
IS
SELECT id, new_date
FROM (SELECT id
, name
, start_date
, end_date
, LEAD( start_date ) OVER( PARTITION BY name ORDER BY start_date ) - 1 AS new_date
FROM demo
WHERE end_date IS NULL)
WHERE new_date IS NOT NULL;
BEGIN
FOR rec IN c_todo
LOOP
UPDATE demo
SET end_date = rec.new_date
WHERE id = rec.id;
END LOOP;
END demo_aius;
/
INSERT INTO demo( id, name, VALUE, start_date )
VALUES ( 6, 'Par_A', 60, TO_DATE( '2016-10-15', 'YYYY-MM-DD' ) );
COMMIT;
SELECT id
, name
, start_date
, end_date
FROM demo
ORDER BY id;
就像脚本显示的那样,这样的更新甚至可以处理多个丢失的结束日期,以防触发器被意外禁用。 “PARTITION BY name”部分确保它在复杂的插入语句之后也能正常工作。
顺便说一句,我同意触发器中的自治事务是最后的手段。我试图通过控制用户界面并将所有此类功能放入包中来避免触发。
【讨论】:
我已经尝试过这个解决方案,但我仍然有 ora-04091 错误。 PL/SQL Developer 标记定义光标 c_todo 的行(选择 id...) 你好 seeksoul,这个触发器只能没有 FOR EACH ROW。如果这不是问题,请将 all 触发器发布到您正在测试的表上【参考方案2】:试试这样的:
create or replace trigger parameter_version
before insert
on parameters
for each row
begin
/*Don't care if there's 0 rows updated */
update parameters
set end_date = :new.start_date - 1
where name = :new.name and end_date is null;
:new.end_date := null;
end;
【讨论】:
你不能在 Oracle 中这样做——这会触发臭名昭著的“表正在变异”错误 好的,既然我有这个错误,我就不确定了。我只需在处理插入的过程/包中执行此操作。以上是关于在 Oracle 插入触发器期间更新以前的记录版本的主要内容,如果未能解决你的问题,请参考以下文章