使用oracle触发器审核50列

Gau*_*oni 2 oracle triggers oracle11g

我需要建立一个triggeroracle 11g用于审核表.

我有一张表50 columns需要audited.

  • 对于every new insert表格,我需要输入一个条目audit table (1 row).
  • 因为every update,假设我更新1st 2nd column,那么它将在审计中创建两个记录 old value and new value.

审计表的结构将是

 id        NOT NULL
 attribute NOT NULL
 OLD VALUE NOT NULL
 NEW VALUE NOT NULL
 cre_date  NOT NULL
 upd_date  NULL
 cre_time  NOT NULL
 upd_time  NULL
Run Code Online (Sandbox Code Playgroud)

在情况下insert,只有主键(主表),即中idcre_date and cre_time需要填充和attribute等于*,在更新的情况下,假设可乐COLB正在更新,那么所有需要被populated.In这种情况下,两个记录将被创建第一条记录的属性colA和相应的old and new值,并且相同colB

现在我的审计解决方案是not very optimized,我创建了一个row level trigger,它将检查该表的每一个50列,无论它是否changed基于它new and old value(if -else),它将填充审计表.我对我的解释不满意,这就是我在这里发帖的原因.我在下面的链接中看到的另一个解决方案:

http://stackoverflow.com/questions/1421645/oracle-excluding-updates-of-one-column-for-firing-a-trigger

这在我的情况下不起作用,我为此做了一个POC,如下所示:

create table temp12(id number);

create or replace trigger my_trigger
after update or insert on temp12
for each row
declare
  TYPE tab_col_nt IS table of varchar2(30);

  v_tab_col_nt tab_col_nt;

begin
 v_tab_col_nt := tab_col_nt('id','name');

   for r in v_tab_col_nt.first..v_tab_col_nt.last
   loop
      if updating(r) then
         insert into data_table values(1,'i am updating'||r);
      else
      insert into data_table values(2,'i am inserting'||r);
      end if;
   end loop;

 end;
Run Code Online (Sandbox Code Playgroud)

如果更新它是调用else部分我不知道为什么.这可以通过compound trigger

Ale*_*ole 5

else始终被调用的直接问题是因为您r直接使用索引变量,而不是查找相关的列名:

for r in v_tab_col_nt.first..v_tab_col_nt.last
loop
    if updating(v_tab_col_nt(r)) then
        insert into data_table values(1,'i am updating '||v_tab_col_nt(r));
    else
        insert into data_table values(2,'i am inserting '||v_tab_col_nt(r));
    end if;
end loop;
Run Code Online (Sandbox Code Playgroud)

你也只显示了id在表中创建列,所以,当r2时,总是会说这是插入name,更新从来没有.更重要的是,如果你确实有一个name列并且只为给定的更新id,那么这段代码会id在未更改时显示为插入.您需要将插入/更新拆分为单独的块:

if updating then
    for r in v_tab_col_nt.first..v_tab_col_nt.last loop
        if updating(v_tab_col_nt(r)) then
            insert into data_table values(1,'i am updating '||v_tab_col_nt(r));
        end if;
    end loop;
else /* inserting */
    for r in v_tab_col_nt.first..v_tab_col_nt.last loop
        insert into data_table values(2,'i am inserting '||v_tab_col_nt(r));
    end loop;
end if;
Run Code Online (Sandbox Code Playgroud)

name即使该列不存在,这仍然会说它正在插入,但我认为这是一个错误,我想user_tab_columns如果你真的想让它变得动态,那么你无论如何都要尝试填充名单.


我同意(至少其中一些)其他人,你可能会更好地使用一个审计表来获取整行的副本,而不是单个列.您的异议似乎是单独列出哪些列更改的复杂性.通过一些工作,您仍然可以通过在需要逐列数据时取消忽略审计表来获取此信息.例如:

create table temp12(id number, col1 number, col2 number, col3 number);
create table temp12_audit(id number, col1 number, col2 number, col3 number,
    action char(1), when timestamp);

create or replace trigger temp12_trig
before update or insert on temp12
for each row
declare
    l_action char(1);
begin
    if inserting then
        l_action := 'I';
    else
        l_action := 'U';
    end if;

    insert into temp12_audit(id, col1, col2, col3, action, when)
    values (:new.id, :new.col1, :new.col2, :new.col3, l_action, systimestamp);
end;
/

insert into temp12(id, col1, col2, col3) values (123, 1, 2, 3);
insert into temp12(id, col1, col2, col3) values (456, 4, 5, 6);
update temp12 set col1 = 9, col2 = 8 where id = 123;
update temp12 set col1 = 7, col3 = 9 where id = 456;
update temp12 set col3 = 7 where id = 123;

select * from temp12_audit order by when;

        ID       COL1       COL2       COL3 A WHEN
---------- ---------- ---------- ---------- - -------------------------
       123          1          2          3 I 29/06/2012 15:07:47.349
       456          4          5          6 I 29/06/2012 15:07:47.357
       123          9          8          3 U 29/06/2012 15:07:47.366
       456          7          5          9 U 29/06/2012 15:07:47.369
       123          9          8          7 U 29/06/2012 15:07:47.371
Run Code Online (Sandbox Code Playgroud)

因此,对于每个采取的操作,两个插入和三个更新,您有一个审计行.但是,您希望查看每个更改的列的单独数据.

select distinct id, when,
    case
        when action = 'I' then 'Record inserted'
        when prev_value is null and value is not null
            then col || ' set to ' || value
        when prev_value is not null and value is null
            then col || ' set to null'
        else col || ' changed from ' || prev_value || ' to ' || value
    end as change
from (
    select *
    from (
        select id,
            col1, lag(col1) over (partition by id order by when) as prev_col1,
            col2, lag(col2) over (partition by id order by when) as prev_col2,
            col3, lag(col3) over (partition by id order by when) as prev_col3,
            action, when
        from temp12_audit
    )
    unpivot ((value, prev_value) for col in (
        (col1, prev_col1) as 'col1',
        (col2, prev_col2) as 'col2',
        (col3, prev_col3) as 'col3')
    )
)
where value != prev_value
    or (value is null and prev_value is not null)
    or (value is not null and prev_value is null)
order by when, id;

        ID WHEN                      CHANGE
---------- ------------------------- -------------------------
       123 29/06/2012 15:07:47.349   Record inserted
       456 29/06/2012 15:07:47.357   Record inserted
       123 29/06/2012 15:07:47.366   col1 changed from 1 to 9
       123 29/06/2012 15:07:47.366   col2 changed from 2 to 8
       456 29/06/2012 15:07:47.369   col1 changed from 4 to 7
       456 29/06/2012 15:07:47.369   col3 changed from 6 to 9
       123 29/06/2012 15:07:47.371   col3 changed from 3 to 7
Run Code Online (Sandbox Code Playgroud)

五项审计记录已变为七项更新; 三个更新语句显示修改了五列.如果你要经常使用它,你可以考虑将它变成一个视图.

所以让我们稍微打破一下.核心是这个内部选择,用于lag()从上一个审计记录中获取该行的先前值id:

        select id,
            col1, lag(col1) over (partition by id order by when) as prev_col1,
            col2, lag(col2) over (partition by id order by when) as prev_col2,
            col3, lag(col3) over (partition by id order by when) as prev_col3,
            action, when
        from temp12_audit
Run Code Online (Sandbox Code Playgroud)

这为我们提供了一个临时视图,其中包含所有审计表列和滞​​后列,然后用于unpivot()操作,您可以使用该标记将问题标记为11g:

    select *
    from (
        ...
    )
    unpivot ((value, prev_value) for col in (
        (col1, prev_col1) as 'col1',
        (col2, prev_col2) as 'col2',
        (col3, prev_col3) as 'col3')
    )
Run Code Online (Sandbox Code Playgroud)

现在我们有一个临时视图,其中包含id, action, when, col, value, prev_value列; 在这种情况下,因为我只有三列,它具有审计表中行数的三倍.最后,外部选择过滤器查看仅包含值已更改的行,即where value != prev_value(允许空值).

select
    ...
from (
    ...
)
where value != prev_value
    or (value is null and prev_value is not null)
    or (value is not null and prev_value is null)
Run Code Online (Sandbox Code Playgroud)

case只是打印一些东西,但当然你可以用数据做任何你想做的事情.这distinct是必需的,因为insert审计表中的条目也在unpivoted视图中转换为三行,并且我在第一个子句中显示了所有三个相同的文本case.

  • 值得一提的是,既然 Oracle 捆绑了闪回数据存档(他们的日志功能),自 11.2.0.4 以来的所有版本都免费,那么任何人都不应该构建自己的审计系统。[了解更多](https://oracle-base.com/articles/12c/flashback-data-archive-fda-enhancements-12cr1) (2认同)