Using OLD and NEW object for dynamic operations inside trigger

好久不见. 提交于 2019-12-11 09:25:53

问题


I want to know whether I can use the OLD and NEW objects for dynamic operations inside trigger.

What I am looking for is something like this :-

  1. ABC is a table for which I need to write Trigger.
  2. TracK_Table maintains list of columns for table which need to be tracked (logged).
  3. f_log is a function that inserts changes in data into a tracking(log) table.

    CREATE OR REPLACE TRIGGER trg_TRACK
    AFTER INSERT OR UPDATE OR DELETE ON ABC
    FOR EACH ROW
    declare
        v_old_val varchar2(1000);
        v_new_val varchar2(1000);
        n_ret int;
        n_id varchar(50);
    
        cursor cur_col is 
        SELECT  COLUMN_NAME,
            TABLE_name      
        FROM    track_TABLE
        WHERE   upper(TABLE_NAME) = upper('ABC') 
          AND exists (select  cname 
                        from col
                       where UPPER(tname) =upper('ABC') 
                         and upper(cname)=upper(COLUMN_NAME))
          AND upper(allow) = 'Y';
    
    begin
       n_id:= :old.id;
    
       for i_get_col in c_get_col
       loop
          execute immediate 
             'begin 
                :v_old_val:= select '||i_get_col.column_name ||' 
                               from '||:old ||' 
                              where id = '||n_id ||'; 
              end;' using out v_old_val;
          execute immediate 
               'begin 
                 :v_new_val:= select '||i_get_col.column_name ||' 
                                from '||:new ||' 
                                where id = '||n_id ||'; 
                end;' using out v_new_val;
          n_ret := f_log(n_id,i_get_col.column_name,v_old_val,v_new_val);
       end loop;
    end;
    /
    

回答1:


One Option: Push the logic to check if a column is being tracke into the f_log procedure and then pass across all of the columns.

For example, if your track_Table holds (table_name, column_name, allow) values for each column that you want to trackm then something like this

 CREATE OF REPLACE PROCEDURE f_log(  p_id          varchar2  
                                    ,p_table_name  varchar2
                                    ,p_column_name varchar2
                                    ,p_old_val     varchar2
                                    ,p_new_val     varchar2)
 as
    l_exists number;
    cursor chk_column_track IS
        SELECT  1
        FROM    track_TABLE           
        WHERE   upper(TABLE_NAME)  = upper(p_table_name)              
        AND     UPPER(column_name) = upper(p_column_name)
        AND     upper(allow) = 'Y'; 
 begin
    open chk_column_track;
    fetch chk_column_track into l_exists;
    if chk_column_track%found then
       --do the insert here
    end if;
    close chk_column_track;
 end;
 /

 CREATE OR REPLACE TRIGGER trg_TRACK 
 AFTER INSERT OR UPDATE OR DELETE ON ABC 
 FOR EACH ROW 
 DECLARE   
      n_id varchar(50); 
 BEGIN   
    n_id := NVL(:old.id, :new.id);   
    -- send all of the values to f_log and have it decide whether to save them
    f_log(:old.id,'COL1',:old.col1,:new.col1);   
    f_log(:old.id,'COL2',:old.col2,:new.col2);   
    f_log(:old.id,'COL3',:old.col3,:new.col3);   
    ... 
 END; 

And for goodness sake, upper-case the values in your track_table on insert so that you don't have to UPPER() the stored values thus making any index on those values useless!

Now, this will chew up some resources checking each column name on each operation, but if you are not running high-volumes then it might be manageable.

Otherwise you will need a more elegant solution. Like leveraging the power of collections and the TABLE() clause to do the track_table lookup in a bulk operation. Bear in mind that I am away from my database at the moment, so I have not test-compiled this code.

    CREATE OR REPLACE TYPE t_audit_row AS OBJECT (
   p_table_name   varchar2(30)
  ,p_column_name  varchar2(30)
  ,p_id           varchar2(50)
  ,p_old_val      varchar2(2000)
  ,p_new_val      varchar2(2000)
);

CREATE OR REPLACE TYPE t_audit_row_table AS TABLE OF t_audit_row;

CREATE OR REPLACE PROCEDURE f_log (p_audit_row_table t_audit_Row_table)
AS
begin
   -- see how we can match the contents of the collection to the values
   -- in the table all in one query. the insert is just my way of showing
   -- how this can be done in one bulk operation. Alternately you could make
   -- the select a cursor and loop through the rows to process them individually.
   insert into my_audit_log (table_name, column_name, id, old_val, new_val)
   select  p_table_name
          ,p_column_name
          ,p_id
          ,p_old_val
          ,p_new_val
   FROM   track_TABLE TT
         ,table(p_audit_row_table) art
   WHERE  tt.TABLE_NAME   = art.p_table_name                       
   AND    tt.column_name  = art.p_column_name         
   AND    tt.allow        = 'Y';
end;
/

CREATE OR REPLACE TRIGGER trg_TRACK   
AFTER INSERT OR UPDATE OR DELETE ON ABC   
FOR EACH ROW   
DECLARE          
  l_id           varchar(50);   
  l_audit_table  t_audit_row_table;
BEGIN        
  l_id := NVL(:old.id, :new.id);        
  -- send all of the values to f_log and have it decide whether to save them   
  l_audit_table := t_audit_row_table (
                      t_audit_row ('ABC','COL1',l_id, :old.col1, :new.col1)  
                      ,t_audit_row ('ABC','COL2',l_id, :old.col2, :new.col2)  
                      ,t_audit_row ('ABC','COL3',l_id, :old.col3, :new.col3)  
                      ,...  
                      ,t_audit_row ('ABC','COLn',l_id, :old.coln, :new.coln)  
                   );
  f_log(l_audit_table);
end;
/



回答2:


No, you cannot access the OLD and NEW pseudo-variables dynamically. What you can do is use your track_table data in a script or procedure to generate static triggers that look like:

CREATE OR REPLACE TRIGGER trg_TRACK
AFTER INSERT OR UPDATE OR DELETE ON ABC
FOR EACH ROW
DECLARE
  n_id varchar(50);
BEGIN
  n_id := NVL(:old.id, :new.id);
  f_log(:old.id,'COL1',:old.col1,:new.col1);
  f_log(:old.id,'COL3',:old.col3,:new.col3);
  ...
END;

So if the data in the TRACK_CHANGES table changes you just have to re-generate the triggers.



来源:https://stackoverflow.com/questions/9871650/using-old-and-new-object-for-dynamic-operations-inside-trigger

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!