更新触发器之前或之后更改同一表中的值(oracle)

时间:2018-02-19 07:24:43

标签: sql oracle plsql triggers

我有3个字段的表格:

--------------------------------------------------
| ID | FILE        |          STATUS             |
--------------------------------------------------
| 1  | my.exe      |           valid             |
--------------------------------------------------
| 2  | my.exe      |           invalid           |
--------------------------------------------------
| 3  | my.exe      |           invalid           |
--------------------------------------------------
| 4  | my.exe      |           invalid           |
--------------------------------------------------

这是一个文件的某些版本。当我将状态更新为“有效”时,我需要所有具有此名称的文件将其状态更改为“无效”, 除了一个更新:

--------------------------------------------------
| ID | FILE        |          STATUS             |
--------------------------------------------------
| 1  | my.exe      |           invalid           |
--------------------------------------------------
| 2  | my.exe      |           invalid           |
--------------------------------------------------
| 3  | my.exe      |            valid            |
--------------------------------------------------
| 4  | my.exe      |           invalid           |
--------------------------------------------------

我认为可以在更新触发器之前完成:

create or replace trigger ChangeValid
  before update
  on mytable 
  for each row
declare

begin
   update mytable t set t.status = 'ivalid' where t.status = 'valid' and t.file = :new.file;

end ChangeValid;

但我很乐意接受ORA-04091。是否可以使用触发器更改此表中的值?

1 个答案:

答案 0 :(得分:3)

这是变异表问题。它发生的原因是因为您编写了一个UPDATE触发器,它尝试在同一个表上执行UPDATE语句。当您更新其他行时,您认为会发生什么?触发器尝试触发,这意味着它以递归方式执行update语句。 Oracle通过禁止行级触发器来短路废话,这些触发器在他们自己的表上行动并投掷ORA-04091

您可以使用复合触发器解决此问题:

create or replace trigger ChangeValid
for update on mytable compound trigger 

  type rec_nt is table of mytable%rowtype;
  recs rec_nt;

  before statement is 
  begin
    recs := rec_nt();
  end before statement;

  before each row is 
  begin
    null;
  end before each row;

  after each row is 
  begin
    recs.extend();
    recs(recs.count()).id := :new.id;
    recs(recs.count()).file_name := :new.file_name;
    recs(recs.count()).status := :new.status;
  end after each row;

  after statement is 
  begin

    for idx in 1 .. recs.count() loop
      if recs(idx).status = 'valid' then
          update mytable t
          set t.status = 'invalid'
          where t.file_name = recs(idx).file_name
          and t.status = 'valid'
          and t.id != recs(idx).id;
      end if;
    end loop;  

  end after statement;

end;
/

请注意此触发器将运行两次:对于您更新的行执行一次,对触发器运行时更​​新的所有行运行一次。这就是为什么我们需要在after statement部分的UPDATE周围的IF语句。您还需要注意UPDATE不会触发递归。

因此,如果您有my.exe的大量记录,这可能是实现此类逻辑的一种昂贵方式。更好的方法是使用PL / SQL API更新当前status记录的valid,然后将更新应用于目标行。