我需要在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
,则只需要填充主键(主表),即id
和cre_date and cre_time
,attribute
等于*
,在更新的情况下,假设colA和colB正在更新,则需要填充所有内容。在这种情况下,将创建两个记录,其中包含第一个记录colA
的属性和相应的old and new
值,并且相同colB
现在我的审核解决方案是not very optimized
,我创建了一个row level trigger
,它将检查该表的每一个50列,无论它是changed
还是基于new and old value
其http://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}}
实现这一点答案 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
列,因此当r
为2
时,它始终会说它正在插入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)
我喜欢这样做的方式:
然后你可以随时查询谁做了什么,何时做什么。
答案 4 :(得分:1)
非常非正统的解决方案: (仅当您有权访问系统表时,至少具有SELECT权限)
你知道你桌子的名字。标识表所有者的ID。用户(=所有者)的名字在SYS.USER $中查找。
通过OWNER#(=所有者的ID)和NAME(=表名)在SYS.OBJ $中查找表的对象ID(= OBJ#)。
通过OBJ#查找组成SYS.COL $表的列。您将找到所有列,其ID(COL#)和名称(NAME)。
使用在这些列集上移动的游标编写UPDATE触发器。你只需要编写一次循环的核心。
并结束:我不提供代码,因为细节可能因Oracle版本而异。
这是真正的动态SQL编程。我碰巧在相当大的企业系统上使用它(团队领导者不知道它)并且它有效。它快速可靠。 缺点:{特权;可移植性;责任人的不良考虑}。