4

我需要创建一个用于审计表的triggerin 。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,只有主键(主表)即需要填充和id 等于,在更新的情况下,假设 colA 和 colB 正在更新,那么都需要填充。在这种情况下,将创建两条记录第一条记录的属性和对应的值,对于cre_date and cre_timeattribute*colAold and newcolB

现在我的审计解决方案是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;

如果更新它正在调用其他部分,我不知道为什么。这可以通过compound trigger

4

5 回答 5

7

总是被调用的直接问题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

5 条审计记录变成了 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列的临时视图;在这种情况下,因为我只有三列,它的行数是审计表中的三倍。最后,外部选择过滤器仅包含值已更改的行,即 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)

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

于 2012-06-29T14:37:22.407 回答
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;

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

于 2012-06-29T12:50:54.773 回答
2

我看到逐个字段审计的唯一方法是检查每个字段 :OLD 和 :NEW 值,并将适当的记录写入审计表。您可以通过在触发器中设置一个子例程来半自动化此操作,您可以将适当的值传递给该子例程,但是我相信您必须以一种或另一种方式为每个单独的字段编写代码。除非其他人有一种出色的方法来使用某种我不知道的反射 API 来做到这一点(并且“我不知道的东西”每天都适用于更多的东西,或者看起来是这样 :-)。

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

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

只是出于好奇,当 INSERT 发生时,将在单行中为 OLD_VALUE 和 NEW_VALUE 放入什么?

分享和享受。

于 2012-06-29T13:55:59.897 回答
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 版本而异。

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

于 2013-01-03T13:04:02.307 回答
1

我喜欢这样做的方式:

  1. 创建与现有原始表平行的审计表。
  2. 向此审计表添加时间戳和用户列。
  3. 每当插入或更新原始表时,只需插入审计表即可。
  4. audi 表应该有一个触发器来设置时间戳和用户值 - 所有其他值都作为新值出现。

然后你可以随时查询谁做了什么,什么时候做的。

于 2012-06-29T12:44:44.383 回答