使用oracle触发器审核50列

时间:2012-06-29 12:38:16

标签: oracle triggers oracle11g

我需要在trigger中创建oracle 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

如果是insert,则只需要填充主键(主表),即idcre_date and cre_timeattribute等于* ,在更新的情况下,假设colA和colB正在更新,则需要填充所有内容。在这种情况下,将创建两个记录,其中包含第一个记录colA的属性和相应的old and new值,并且相同colB

现在我的审核解决方案是not very optimized,我创建了一个row level trigger,它将检查该表的每一个50列,无论它是changed还是基于new and old valuehttp://stackoverflow.com/questions/1421645/oracle-excluding-updates-of-one-column-for-firing-a-trigger(if -else),它将填充审计表。 我对我的解释不满意,这就是我在这里发帖的原因。 我在下面的链接中看到的另一个解决方案:

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;

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

compound trigger

如果要更新,则调用else部分我不知道为什么。    可以通过{{1}}

实现这一点

5 个答案:

答案 0 :(得分: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;

您还只在表格创建中显示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;

即使该列不存在,仍会说它正在插入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

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

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

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

所以让我们稍微打破一下。核心是这个内部选择,它使用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

这为我们提供了一个临时视图,其中包含所有审计表列和滞​​后列,然后用于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')
    )

现在我们有一个临时视图,其中包含id, action, when, col, value, prev_value列;在这种情况下,因为我只有三列,它具有审计表中行数的三倍。最后,查看的外部选择过滤器仅包含值已更改的行,即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)

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

答案 1 :(得分:3)

为什么不让生活更轻松,并在更新任何列中的任何数据时插入整行。因此,主表上的任何更新(或通常删除)都会将原始行首先复制到审计表。因此,您的审计表将具有与主表相同的布局,但是具有额外的几个跟踪字段,例如:

create or replace trigger my_tab_tr
before update or delete
on my_tab
referencing new as new and old as old
for each row
declare
  l_type varchar2(3);
begin
  if (updating) then
    l_type = 'UPD';
  else
    l_type = 'DEL';
  end if;

insert into my_tab_audit(
 col1,
 col2,
 audit_type,
 audit_date) 
values (
 :old.col1,
 :old.col2,
 l_type,
 sysdate
);
end;

根据需要将其他列添加到审计表中,这只是一个典型示例

答案 2 :(得分:2)

我看到逐个字段审核的唯一方法是检查每个字段:OLD和:相互之间的新值,并将相应的记录写入审计表。您可以通过在触发器中为您传递适当值的子例程来半自动化,但不管怎样,我相信您将不得不为每个字段编写代码。除非其他人有一个很好的方法来做某些我不知道的反射API(并且“我不知道的”每天适用于更多的东西,或者似乎是:-)。

是否审核单个字段或审核整行(我通常称之为“历史”表)的选择取决于您打算如何使用数据。在这种情况下,需要报告单个字段更改的情况,我同意逐个字段的审核似乎更合适。在其他情况下(例如,数据提取必须在任何给定日期可重现),逐行审计或“历史表”方法更适合。

无论审计级别(逐字段还是逐行),都需要仔细编写比较逻辑来处理NULL / NOT NULL情况,这样就不会因为比较而受到攻击:OLD.FIELD1 = :NEW.FIELD1其中一个值(或两者)为NULL,并且最终没有采取适当的操作,因为NULL不等于任何东西,甚至本身。不要问我怎么知道...: - )

出于好奇,将在INSERT发生时创建的单行中为OLD_VALUE和NEW_VALUE添加什么内容?

分享并享受。

答案 3 :(得分:1)

我喜欢这样做的方式:

  1. 创建一个与现有原始版本平行的审核表 表。
  2. 将时间戳和用户列添加到此审核表。
  3. 每当插入或更新原始表时,只需插入即可 进入审计表。
  4. audi表应该有一个触发器来设置时间戳和用户值 - 所有其他值都作为新值出现。
  5. 然后你可以随时查询谁做了什么,何时做什么。

答案 4 :(得分:1)

非常非正统的解决方案: (仅当您有权访问系统表时,至少具有SELECT权限)

  1. 你知道你桌子的名字。标识表所有者的ID。用户(=所有者)的名字在SYS.USER $中查找。

  2. 通过OWNER#(=所有者的ID)和NAME(=表名)在SYS.OBJ $中查找表的对象ID(= OBJ#)。

  3. 通过OBJ#查找组成SYS.COL $表的列。您将找到所有列,其ID(COL#)和名称(NAME)。

  4. 使用在这些列集上移动的游标编写UPDATE触发器。你只需要编写一次循环的核心。

  5. 并结束:我不提供代码,因为细节可能因Oracle版本而异。

  6. 这是真正的动态SQL编程。我碰巧在相当大的企业系统上使用它(团队领导者不知道它)并且它有效。它快速可靠。 缺点:{特权;可移植性;责任人的不良考虑}。