在Oracle插入触发器期间更新以前的记录版本

时间:2016-10-05 14:24:05

标签: sql oracle plsql oracle11g

这是我在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(关闭版本)并且在插入记录时我有一个下一个param版本,其中包含start_date = sysdate。

我有一个ORA-04091错误'表名正在变异,触发/功能可能看不到它。'

我知道这个案子很难,可能不可能但也许有人知道解决方案? 或者可能存在另一种解决方案?

2 个答案:

答案 0 :(得分: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;

与脚本显示类似,如果触发器被意外禁用,这样的更新甚至可以处理多个丢失的结束日期。 &#34; PARTITION BY名称&#34; part确保它在复杂的insert语句之后也起作用。

顺便说一下,我同意触发器中的自主交易是最后的选择。我试图通过控制用户界面并将所有这些功能放在包中来避免触发器。

答案 1 :(得分:0)

尝试这样的事情:

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;