【问题标题】:Trigger for only changed values仅触发更改的值
【发布时间】:2012-06-27 09:35:37
【问题描述】:

假设我们在表中有 3 条记录:orig_tab

---------------------------------------------
|  PK  |  Name  |  Address  |  Postal Code  |
---------------------------------------------
|   1  |  AA    |  Street1  |   11111       |
|   2  |  BB    |  Street2  |   22222       |
|   3  |  CC    |  Street3  |   33333       |
---------------------------------------------

现在数据变了:

---------------------------------------------
|  PK  |  Name  |  Address  |  Postal Code  |
---------------------------------------------
|   1  |  AA    |  Street1  |   11111       |
|   2  |  BB    |  Street2  |   44444       |
|   3  |  CC    |  Dtreet7  |   33333       |
---------------------------------------------

客户想要的是更新记录和更新的列(是的,我知道这没有任何意义,但他们使用的是 1970 年代的一些旧系统,他们想做一些日志记录等)。所以报表应该是这样的:

---------------------------------------------
|  PK  |  Name  |  Address  |  Postal Code  |
---------------------------------------------
|   2  |        |           |   44444       |
|   3  |        |  Dtreet7  |               |
---------------------------------------------

这是我试过的:

CREATE OR REPLACE TRIGGER vr_reporting_trigger
   AFTER UPDATE ON orig_tab
   FOR EACH ROW
BEGIN
IF inserting THEN
INSERT INTO rep_tab(pk, name, address, code)
  SELECT :new.pk, :new.name, :new.address, :new,code FROM DUAL
  WHERE NOT EXISTS (SELECT 1 FROM rep_tab WHERE pk = :new.pk);
UPDATE rep_tab t SET t.name = :new.name, t.address = :new.address, t.code = :new.code 
   WHERE t.pk = :new.pk;
ELSIF updating THEN
IF :new.pk <> :old.pk THEN
     UPDATE rep_tab t
        SET t.name = :new.name, t.address = :new.address, t.code =: new.code
      WHERE t.pk = :old.pk ;
  END IF;
  MERGE INTO rep_tab d
  USING DUAL ON (d.pk = :old.pk)
  WHEN MATCHED THEN
  UPDATE SET d.name = :new.name, d.address = :new.address, d.code =: new.code
  WHEN NOT MATCHED THEN
  INSERT (d.pk,d.name, d.address, d.code) VALUES (:new.pk,:new.name, new.address, new.code);
END IF;
END;

通过这个解决方案,我得到:

---------------------------------------------
|  PK  |  Name  |  Address  |  Postal Code  |
---------------------------------------------
|   2  |  BB    |  Street2  |   44444       |
|   3  |  CC    |  Dtreet7  |   33333       |
---------------------------------------------

我知道它在更新语句时插入子句中的某处,但我不知道如何根据我的要求更改此子句。有什么建议吗?

提前致谢。

【问题讨论】:

    标签: oracle plsql triggers oracle11g


    【解决方案1】:

    你需要这个:

    在 UPDATE 触发器中,可以使用 UPDATING 指定列名 条件谓词来确定命名列是否正在 更新。例如,假设触发器定义如下:

    CREATE OR REPLACE TRIGGER ...
    ... UPDATE OF Sal, Comm ON Emp_tab ...
    BEGIN
    
    ... IF UPDATING ('SAL') THEN ... END IF;
    
    END;
    

    来自Oracle documentation(9i)

    11gR2 documentation

    【讨论】:

    • +1,但是 9i 文档? 这是11g version
    • 如果 yolumn 是 UPDATE 语句的一部分,这似乎会触发。不仅在值发生变化时。
    【解决方案2】:

    仅触发更改的值。

    作为一个例子(仅NOT NULL 支持)(带有 NULL 的比较运算符总是返回 FALSE):

    CREATE OR REPLACE TRIGGER trigger_department_update
      BEFORE UPDATE OF 
          department_id,
          cluster_id, 
          division_id, 
          macroregion_id, 
          address, 
          email
        ON cl_department
      FOR EACH ROW
    BEGIN
      IF (
        :new.department_id != :old.department_id
        OR :new.cluster_id != :old.cluster_id
        OR :new.division_id != :old.division_id
        OR :new.macroregion_id != :old.macroregion_id
        OR :new.address != :old.address
        OR :new.email != :old.email
      ) 
        THEN :new.update_date := CURRENT_TIMESTAMP; 
      END IF;
    END;
    

    支持 NULL(感谢本文下方 cmets 中的@Oleg)(感谢@Chris Bandy 提供answer):

    CREATE OR REPLACE TRIGGER trigger_department_update
      BEFORE UPDATE OF 
          department_id,
          cluster_id, 
          division_id, 
          macroregion_id, 
          address, 
          email
        ON cl_department
      FOR EACH ROW
    BEGIN
      IF (
        ((:new.department_id <> :old.department_id OR :new.department_id IS NULL OR :old.department_id IS NULL) AND NOT (:new.department_id IS NULL AND :old.department_id IS NULL))
        OR ((:new.cluster_id <> :old.cluster_id OR :new.cluster_id IS NULL OR :old.cluster_id IS NULL) AND NOT (:new.cluster_id IS NULL AND :old.cluster_id IS NULL))
        OR ((:new.division_id <> :old.division_id OR :new.division_id IS NULL OR :old.division_id IS NULL) AND NOT (:new.division_id IS NULL AND :old.division_id IS NULL))
        OR ((:new.macroregion_id <> :old.macroregion_id OR :new.macroregion_id IS NULL OR :old.macroregion_id IS NULL) AND NOT (:new.macroregion_id IS NULL AND :old.macroregion_id IS NULL))
        OR ((:new.address <> :old.address OR :new.address IS NULL OR :old.address IS NULL) AND NOT (:new.address IS NULL AND :old.address IS NULL))
        OR ((:new.email <> :old.email OR :new.email IS NULL OR :old.email IS NULL) AND NOT (:new.email IS NULL AND :old.email IS NULL))
      ) 
        THEN :new.update_date := CURRENT_TIMESTAMP; 
      END IF;
    END;
    

    【讨论】:

    • 此解决方案不适用于 NULL 列。带 NULL 的比较运算符总是返回 FALSE。
    • @Oleg 感谢您提供非常有用的评论。更新了答案。
    【解决方案3】:

    对待 null :

    ... nvl(to_char(:new.department_id,' ') &lt;&gt; nvl(to_char(:old.department_id,' ') OR ...

    【讨论】:

    • Oracle 认为空字符串 ('') 为空值,因此 nvl(a_character_expression, '') 没有意义。
    • 这是处理空值比较的最简单和最简洁的答案,尽管请参阅@MaximMandrik 的答案以了解触发器中所需的完整逻辑。
    猜你喜欢
    • 2016-05-03
    • 1970-01-01
    • 2021-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-02
    相关资源
    最近更新 更多