【问题标题】:Is it ever possible to design an Oracle trigger that modifies same table (guaranteed not same row)?是否有可能设计一个修改同一个表(保证不是同一行)的 Oracle 触发器?
【发布时间】:2018-07-26 15:38:25
【问题描述】:

我需要编写一个 TRIGGER,通过将旧值插入或更新到同一表中的另一行 来保留列的旧值。(是的,我知道)。 以下 MERGE/DUAL 诡计对我很有帮助, 但是因为在这种情况下,我要插入或更新同一个表, Oracle 在运行时抱怨。另外,由于某种原因,我发现编写编译时不会出错的代码异常困难。

两个问题:

  1. 是否可以修改触发器所在的同一个表,即使我可以保证触发器永远不会更新触发触发器的行?还是我必须做类似的事情(例如):将待处理的更改插入 another 表,以便第二个触发器可以将它们合并回 original 表? (此表是一个客户界面,因此我无法重新设计它以使用第二个表来永久存储旧值。)
  2. 不允许让我使用 :old.event_key,允许让我使用 的编译器错误是什么: old.property_val 在 MERGE 语句中? (声明一个变量 old_event_key 并将其分配给 :old.event_key 似乎 工作)是否有某种 隐藏中间语言 知道一列何时是主键(部分),并阻止您通过 :old.?
  3. 引用它

这是有问题的代码:

create or replace trigger remember_old_status
  before update on event_properties
  for each row
    when (old.property_name = 'CURRENT-STATUS')
    declare
      old_event_key varchar2(20);
    begin
      old_event_key := :old.event_key;
      merge into event_properties eprop
        using (select 1 from dual) dummy
        on (    eprop.event_key = old_event_key
            AND eprop.property_name = 'PREVIOUS-STATUS')
        when matched then
          update set property_val = :old.property_val
        when not matched then
          insert (event_key, property_name, property_val)
          values (old_event_key, 'PREVIOUS-STATUS', :old.property_val);
    end;

这是表格:

CREATE TABLE "CUST"."EVENT_PROPERTIES" 
 (  "EVENT_KEY" VARCHAR2(20 BYTE) CONSTRAINT "NN_FLE_FLK" NOT NULL ENABLE, 
  "PROPERTY_NAME" VARCHAR2(20 BYTE) CONSTRAINT "NN_FLE_PN" NOT NULL ENABLE, 
  "PROPERTY_VAL" VARCHAR2(80 BYTE), 
   CONSTRAINT "PX_EVENT_PROPERTIES" PRIMARY KEY ("EVENT_KEY", "PROPERTY_NAME") DEFERRABLE
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS 
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "CUST_TS"  ENABLE
 ) SEGMENT CREATION IMMEDIATE 
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 
NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "CUST_TS" ;

以下是错误消息:

ORA-04091:表 CUST.EVENT_PROPERTIES 正在变异,触发器/函数可能看不到它 ORA-06512:在“CUST.REMEMBER_OLD_STATUS”,第 5 行

【问题讨论】:

  • 看看这些例子:oracle-base.com/articles/9i/mutating-table-exceptions。其中一个展示了如何使用“另一个表”(您提到过)——这是一个全局临时表,而第二个选项展示了包变量中的集合的用法。
  • 对 Oracle 文档的任何检查或在 ORA-04091 上搜索此站点都会清楚地给您“否”的答案。您无法在触发for each row 的表上执行 DML。解决这个问题的模式是使用for each row 来更新另一个表。然后让after 语句级触发器使用来自该其他表的信息来更新触发的表。
  • 您在问题的第二部分中指的是哪些编译器错误?直接在代码中使用old.event_key 可以编译。好吧,无论如何,同时提出两个问题是没有帮助的。如果你仍然可以重现它,你应该问一个关于显示实际代码和错误的单独问题。

标签: oracle plsql triggers


【解决方案1】:

您可以使用a compound trigger 来执行此操作,将旧值存储在before each row 部分的变量中,然后合并到after statement

这假设您一次只能更新一行:

create or replace trigger remember_old_status
for update on event_properties
compound trigger

  old_rec event_properties%rowtype;

  before each row is
  begin
    if (:old.property_name = 'CURRENT-STATUS') then
      old_rec.event_key := :old.event_key;
      old_rec.property_name := :old.property_name;
      old_rec.property_val := :old.property_val;
    end if;
  end before each row;

  after statement is
  begin
    if (old_rec.property_name = 'CURRENT-STATUS') then
      merge into event_properties eprop
        using (
          select old_rec.event_key as event_key, 
            'PREVIOUS-STATUS' as property_name,
            old_rec.property_val as property_val
            from dual
        ) dummy
        on (eprop.event_key = dummy.event_key
            and eprop.property_name = dummy.property_name)
        when matched then
          update set property_val = old_rec.property_val
        when not matched then
          insert (event_key, property_name, property_val)
          values (dummy.event_key, dummy.property_name, dummy.property_val);
    end if;
  end after statement;
end remember_old_status;
/

快速测试:

insert into event_properties values('SOME_EVENT', 'CURRENT-STATUS', 'A');

1 row inserted.

update event_properties set property_val = 'B' where event_key = 'SOME_EVENT' and property_name = 'CURRENT-STATUS';

1 row updated.

select * from event_properties;

EVENT_KEY            PROPERTY_NAME        PROPERTY_VAL                                                                    
-------------------- -------------------- --------------------------------------------------------------------------------
SOME_EVENT           CURRENT-STATUS       B                                                                               
SOME_EVENT           PREVIOUS-STATUS      A                                                                               

update event_properties set property_val = 'C' where event_key = 'SOME_EVENT' and property_name = 'CURRENT-STATUS';

1 row updated.

select * from event_properties;

EVENT_KEY            PROPERTY_NAME        PROPERTY_VAL                                                                    
-------------------- -------------------- --------------------------------------------------------------------------------
SOME_EVENT           CURRENT-STATUS       C                                                                               
SOME_EVENT           PREVIOUS-STATUS      B                                                                               

如果您想在一个语句上处理多个更新,则 before each row 可以填充一个集合,然后您可以在 after statement 中使用它。

create or replace trigger remember_old_status
for update on event_properties
compound trigger

  type t_type is table of event_properties%rowtype;
  old_recs t_type := t_type();

  before each row is
  begin
    if (:old.property_name = 'CURRENT-STATUS') then
      old_recs.extend();
      old_recs(old_recs.count).event_key := :old.event_key;
      old_recs(old_recs.count).property_name := :old.property_name;
      old_recs(old_recs.count).property_val := :old.property_val;
    end if;
  end before each row;

  after statement is
  begin
    forall i in old_recs.first..old_recs.last
      merge into event_properties eprop
        using (
          select old_recs(i).event_key as event_key, 
            'PREVIOUS-STATUS' as property_name,
            old_recs(i).property_val as property_val
            from dual
        ) dummy
        on (eprop.event_key = dummy.event_key
            and eprop.property_name = dummy.property_name)
        when matched then
          update set property_val = old_recs(i).property_val
        when not matched then
          insert (event_key, property_name, property_val)
          values (dummy.event_key, dummy.property_name, dummy.property_val);
  end after statement;
end remember_old_status;
/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-15
    • 1970-01-01
    • 1970-01-01
    • 2021-07-25
    • 2013-12-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多