【问题标题】:How to get old values in Compound Trigger Update in oracle如何在 oracle 中的复合触发器更新中获取旧值
【发布时间】:2016-12-01 12:07:18
【问题描述】:

我搜索了我的问题,但还没有找到任何解决方案,所以我在这里发布。我正在使用 Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production。

我有一个 Main_Table 假设有 10 条记录,我有一个 Log_Table 来记录(插入)新表更新时的旧值和新值。

我正在使用复合触发器(以避免变异错误)动态循环“Main_Table”的所有列,并通过过滤 new 获取更新的行记录。主键 (UID))。

我希望我正确使用复合触发器。

我没有使用:old:new,因为我正在动态循环所有列并且需要匹配列值。

But it is again giving me mutating error:
Error report -
SQL Error: ORA-04091: table Main_Table  is mutating, trigger/function may not see it
ORA-06512: at "Schema.TRG_TEST", line 87
ORA-04088: error during execution of trigger 'Schema.TRG_TEST'
04091. 00000 -  "table %s.%s is mutating, trigger/function may not see it"
*Cause:    A trigger (or a user defined plsql function that is referenced in
           this statement) attempted to look at (or modify) a table that was
           in the middle of being modified by the statement which fired it.
*Action:   Rewrite the trigger (or function) so it does not read that table.


Below is my trigger code:

create or replace TRIGGER TRG_TEST
 FOR INSERT or UPDATE ON Main_Table
COMPOUND TRIGGER
  TYPE NUMBER_TABLE IS TABLE OF NUMBER;
  tblTABLE2_IDS  NUMBER_TABLE;
    TYPE VARCHAR_TABLE IS TABLE OF VARCHAR(2000);
  tblTABLE3_IDS  VARCHAR_TABLE;
   TYPE VARCHAR_TABLE1 IS TABLE OF VARCHAR(2000);
  tblTABLE4_IDS  VARCHAR_TABLE1;

  vcount NUMBER;
  colCount NUMBER;
   colCountAfter NUMBER;
  vvalue VARCHAR2(4000);
  vcolumn VARCHAR2(4000);
  sql1 VARCHAR2(4000);
  dynamicq varchar(4000);
testv varchar(2000);
testv1 varchar(2000);
ssql varchar(2000);
ssql1 varchar(2000);
maxsiteid NUMBER;
newsid varchar(2000);
newstid varchar(2000);
newuid varchar(2000);
 --Executed before DML statement

  BEFORE STATEMENT IS
  BEGIN
 tblTABLE2_IDS := NUMBER_TABLE();
    tblTABLE3_IDS:=  VARCHAR_TABLE();
    tblTABLE4_IDS:=  VARCHAR_TABLE1();
     IF UPDATING THEN
     dbms_output.put_line('Before Statement - In Updating'); 
         --dbms_output.put_line('Before Each Row - In Updating'); 
        -- tblTABLE2_IDS.EXTEND;
      --tblTABLE2_IDS(tblTABLE2_IDS.LAST) := :new.UID;
      END IF;
  END BEFORE STATEMENT;

  --Executed before each row change- :NEW, :OLD are available
     BEFORE EACH ROW IS
     BEGIN
        IF UPDATING THEN
         dbms_output.put_line('Before Each Row - In Updating'); 
         tblTABLE2_IDS.EXTEND;
      tblTABLE2_IDS(tblTABLE2_IDS.LAST) := :new.UID;
     -- FOR columnlist IN
      --(SELECT COLUMN_NAME AS COLUMN_NAME  FROM all_tab_columns  WHERE lower(TABLE_NAME) = 'Main_Table'
      -- AND lower(COLUMN_NAME) NOT IN ( 's_id' ,'msid' ,'st' ,'u_id' ,'db_flag' )) 

      --LOOP
      --colCount:=colCount+1;
      --ssql1:='select '||columnlist.COLUMN_NAME||' from Main_Table where UID='||tblTABLE2_IDS(tblTABLE2_IDS.LAST)||'';
      --dbms_output.put_line(ssql1);
      --execute immediate ssql1 into testv;
      --tblTABLE3_IDS(colCount):=testv;
      --dbms_output.put_line(testv);
      --END LOOP;
           END IF;
     END BEFORE EACH ROW;

 --Executed aftereach row change- :NEW, :OLD are available
  AFTER EACH ROW IS
  BEGIN

    IF UPDATING THEN
         dbms_output.put_line('After Each Row - In Updating'); 
      FOR columnlist IN
      (SELECT COLUMN_NAME AS COLUMN_NAME  FROM all_tab_columns  WHERE lower(TABLE_NAME) = 'Main_Table'
       AND lower(COLUMN_NAME) NOT IN ( 's_id' ,'msid' ,'st' ,'u_id' ,'db_flag' )) 

      LOOP
      colCount:=colCount+1;
      ssql1:='select '||columnlist.COLUMN_NAME||' from Main_Table where UID='||tblTABLE2_IDS(tblTABLE2_IDS.LAST)||'';
      dbms_output.put_line(ssql1);
      execute immediate ssql1 into testv;
      tblTABLE3_IDS(colCount):=testv;
      dbms_output.put_line(testv);
      END LOOP;
           END IF;
  END AFTER EACH ROW;

--Executed after DML statement
  AFTER STATEMENT IS
  BEGIN

      IF UPDATING THEN
            dbms_output.put_line('After Statement - In Updating'); 
          FOR columnlist IN
      (SELECT COLUMN_NAME AS COLUMN_NAME  FROM all_tab_columns  WHERE lower(TABLE_NAME) = 'Main_Table'
       AND lower(COLUMN_NAME) NOT IN ( 's_id' ,'msid' ,'st' ,'u_id' ,'db_flag' )) 

      LOOP
      colCountAfter:=colCountAfter+1;
      dbms_output.put_line('loop started'); 

       ssql1:='select '||columnlist.COLUMN_NAME||' from Main_Table where UID='||tblTABLE2_IDS(tblTABLE2_IDS.LAST)||'';

      execute immediate ssql1 into testv1;
      dbms_output.put_line(testv);
      tblTABLE3_IDS(colCountAfter):=testv1;

       IF ((testv) IS NOT NULL) THEN
       FOR i IN tblTABLE3_IDS.FIRST..tblTABLE2_IDS.LAST LOOP
       dbms_output.put_line('Values No :' ||i||' is ' || tblTABLE3_IDS(i) || ' and ' ||tblTABLE4_IDS(i));
       IF(tblTABLE3_IDS(i)=tblTABLE4_IDS(i)) THEN
         dbms_output.put_line(testv1);


       ELSE
       -- dbms_output.put_line('select :new.'|| columnlist.COLUMN_NAME||' from dual');
        dbms_output.put_line(testv1);           


         INSERT INTO Log_Table
              (
                user_id,  
                log_action,
                log_table_name,
                schema_name,
                log_column_name,
                col_old_val,
                col_new_val,
                ne_type,
                ne_id,
                system_id
              )
              VALUES
              (
                newuid,
                'UPDATE',
                'Main_Table',
                'SCHEMA'
                ,columnlist.COLUMN_NAME
                ,tblTABLE3_IDS(i)
                ,tblTABLE4_IDS(i)
                ,'S'
                ,newstid
                ,newsid
              );
       END IF;
       END LOOP;


       END IF;
      END LOOP;
           END IF;

  END AFTER STATEMENT;
  END TRG_TEST;

最初我尝试在“Before Each Row”中访问更新表,然后我尝试在“After Each Row”中访问它,两种情况下都出现同样的错误。

即使在使用复合触发器后,我仍在努力寻找解决方案,但是我在插入时实现了相同的效果。

任何人都可以帮助如何实现它。提前致谢。

【问题讨论】:

  • 请发布数据库版本和版本。我假设你重新邀请轮子。有一些 Oracle 产品可以做到这一点。
  • 我使用的是 Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production。
  • 没有。就是不行。这种在运行时为每条 DML 语句生成、编译和运行动态 SQL 的想法真的很糟糕。如果您必须使用触发器来跟踪所有更改,请分别为每个表生成触发器代码。相信我,你会为自己省去很多痛苦和麻烦。

标签: sql oracle triggers oracle12c beforeupdate


【解决方案1】:

通常,语句后行级触发器足以填充日志记录表。 :old 和 :new 伪记录中提供新旧列值。

一个非常基本的例子:

create or replace trigger my_trigger
   after insert or update on my_table
   for each row
declare

    begin

       if inserting
       then
          insert into logging table
             (id
             ,my_column_old
             ,my_column_new)
          values
             (:new.id
             ,null      -- no old value in case of insert
             ,:new.my_column);
       else
          insert into logging table
             (id
             ,my_column_old
             ,my_column_new)
          values
             (:new.id
             ,:old.my_column
             ,:new.my_column);
       end if;
    end;

【讨论】:

  • 嗨 Rene,感谢您的回答,请查看我编辑的问题。我无法使用新旧,因为我正在动态获取表格列名。
  • 我需要先动态检查所有列值,然后与新值匹配。(all_columns) 值,如果有差异,我将用旧值和新值记录差异。我不知道列运行时的名称。
  • 你可以在没有动态语句的情况下做到这一点。只需为您需要检查的每一列进行编程即可。触发代码本身的过多计算会减慢您的应用程序。
  • My_Table 有近 100 列,还有 50 个这样的表。我正在寻找一种通用触发器代码的通用解决方案,它可以一次性适用于所有表。(我只需要更改是每个表的表名)
  • 也许可以创建一个脚本来生成触发代码。顺便提一句。 Oracle 让您检查特定列的更新。 stackoverflow.com/questions/11201045/…
【解决方案2】:

据我了解,您希望跟踪新添加列的历史记录(用户可以添加和删除列)并且您不想重新编译触发器。

那么你可以使用Oracle Total Recall

很高兴您拥有 Oracle 12c EE(大多数错误已修复)。

【讨论】:

  • 谢谢。我看到它是一个需要理解的新概念;我会研究它,但现在由于某种原因,由于时间限制,我不打算实现它。目前,一个 sql 触发器帮助将拯救我的一天。
  • 另外,我想跟踪任何列新旧更新值的历史记录,而不是列本身。
猜你喜欢
  • 2017-03-28
  • 1970-01-01
  • 2016-05-02
  • 2020-12-30
  • 2017-07-05
  • 1970-01-01
  • 1970-01-01
  • 2012-02-08
  • 2013-05-25
相关资源
最近更新 更多