[英]How To Create Generic SQL Server Stored Procedure To Perform Inserts Into Audit Table Based on Inserted and Deleted In Trigger
我已根据以下帖子的第一个答案提供的信息实施了审计跟踪框架:
SQL Server历史记录表 - 通过SP或Trigger填充?
最终,我实现的框架使用每个表三个触发器,根据表的更改插入审计信息。
我的插入和删除审计触发器非常简单。 但是,更新触发器要复杂得多,因为触发器必须检查以确定每个列是否处于审计控制之下,然后根据Inserted和Deleted列中的列值是否相等来执行插入操作。我不想写不必要的审计记录。 最后,我想知道是否有办法编写一个存储过程,通过允许我动态执行下面的insert语句来减少触发器中的代码量。 基本上,我设想触发器使用处于审计控制下的每个列名触发sproc,然后存储过程将使用列名来执行下面的代码片段。 目前,对于审计控制下的每一列,我都有以下代码,遗憾的是这会产生大量冗余代码。
建议更改后修改触发器
CREATE TRIGGER [dbo].[Audit_Customers_Update] ON [dbo].[Customers]
FOR UPDATE AS
select FirstName,LastName into #deleted from deleted;
declare /*const*/ @TABLE_NAME sysname = '[table name]';
declare f cursor
local
forward_only
read_only
for
select c.name, quotename(c.name, '[')
from
sys.columns c
inner join sys.types t on c.system_type_id = t.system_type_id
where
c.object_id = object_id(@TABLE_NAME)
and c.is_computed = 0
and c.is_identity = 0
and t.name not in ('text', 'image', 'timestamp', 'xml')
and (substring(COLUMNS_UPDATED(), ((c.column_id - 1) / 8) + 1, 1) & power(2, (c.column_id - 1) % 8)) > 0
;
declare @field_name sysname, @field_name_sanitised sysname;
create table #results (row_id int not null,
field_name sysname not null,
oldval nvarchar(150) null,
newval nvarchar(150) null);
-- For each changed field, insert what exactly changed into #results
open f;
fetch next from f into @field_name, @field_name_sanitised;
while @@fetch_status = 0
begin
declare @query nvarchar(4000);
set @query = N'insert into #results(row_id, field_name, oldval, newval)
select d.row_id, @field_name, d.' + @field_name_sanitised + N', i.' + @field_name_sanitised + N'
from
#deleted d inner join ' + @TABLE_NAME + N' i on d.row_id = i.row_id
where
(d.' + @field_name_sanitised + N' <> i.' + @field_name_sanitised + N')
or
(case when d.' + @field_name_sanitised + N' is null then 1 else 0 end <> case when i.' + @field_name_sanitised + N' is null then 1 else 0 end);'
;
exec sp_executesql
@stmt = @query,
@params = N'@field_name sysname',
@field_name = @field_name
;
fetch next from f into @field_name, @field_name_sanitised;
end;
close f;
deallocate f;
-- Do something meaningful to #results here
我如何访问#results? 我必须使用光标吗?
我们通过以下方式解决了这个问题。
select <list of tracked columns here> into #deleted from deleted;
declare /*const*/ @TABLE_NAME sysname = '[table name]';
declare f cursor
local
forward_only
read_only
for
select c.name, quotename(c.name, '[')
from
sys.columns c
inner join sys.types t on c.system_type_id = t.system_type_id
where
c.object_id = object_id(@TABLE_NAME)
and c.is_computed = 0
and c.is_identity = 0
and t.name not in ('text', 'image', 'timestamp', 'xml')
and (substring(COLUMNS_UPDATED(), ((c.column_id - 1) / 8) + 1, 1) & power(2, (c.column_id - 1) % 8)) > 0
;
declare @field_name sysname, @field_name_sanitised sysname;
create table #results (row_id int not null, field_name sysname not null, oldval nvarchar(150) null, newval nvarchar(150) null);
-- For each changed field, insert what exactly changed into #results
open f;
fetch next from f into @field_name, @field_name_sanitised;
while @@fetch_status = 0
begin
declare @query nvarchar(4000);
set @query = N'insert into #results(row_id, field_name, oldval, newval)
select d.row_id, @field_name, d.' + @field_name_sanitised + N', i.' + @field_name_sanitised + N'
from
#deleted d inner join ' + @TABLE_NAME + N' i on d.row_id = i.row_id
where
(d.' + @field_name_sanitised + N' <> i.' + @field_name_sanitised + N')
or
(case when d.' + @field_name_sanitised + N' is null then 1 else 0 end <> case when i.' + @field_name_sanitised + N' is null then 1 else 0 end);'
;
exec sp_executesql
@stmt = @query,
@params = N'@field_name sysname',
@field_name = @field_name
;
fetch next from f into @field_name, @field_name_sanitised;
end;
close f;
deallocate f;
-- Do something meaningful to #results here
相关阅读:
陷入类似的问题......用这种方式解决......可能不是最优雅的解决方案,但适用于合规人员...所以这里......
基于此处给出的解决方案
xml是从更新表的触发器中用FOR XML提取的......“OldValues”来自DELETED表和来自INSERTED表的“NewValues”...所以最终的xml看起来像这样......
DECLARE @x XML= '<FieldData>
<UpdatedColumns>
<trType>OldValues</trType>
<ID>5</ID>
<def_label>TEST_TIE</def_label>
<def_code />
</UpdatedColumns>
<UpdatedColumns>
<trType>OldValues</trType>
<ID>4</ID>
<def_label>RP_TIE</def_label>
<def_code />
</UpdatedColumns>
<UpdatedColumns>
<trType>OldValues</trType>
<ID>3</ID>
<def_label>ERR_TIE</def_label>
<def_code />
</UpdatedColumns><UpdatedColumns>
<trType>NewValues</trType>
<ID>5</ID>
<def_label>TEST_TIE</def_label>
<def_code>A</def_code>
</UpdatedColumns>
<UpdatedColumns>
<trType>NewValues</trType>
<ID>4</ID>
<def_label>RP_TIE</def_label>
<def_code>A</def_code>
</UpdatedColumns>
<UpdatedColumns>
<trType>NewValues</trType>
<ID>3</ID>
<def_label>ERR_TIE</def_label>
<def_code>A</def_code>
</UpdatedColumns>
</FieldData>'
declare @timestamp datetime2= SYSDATETIME()
select
ID = identity(int,1,1),
T.N.value('local-name(.)', 'nvarchar(100)') as NodeName,
T.N.value('../ID[1]','nvarchar(100)') AS table_ID,
T.N.value('.', 'nvarchar(100)') as OldValue
INTO #old
from @x.nodes('//UpdatedColumns/*') as T(N)
WHERE T.N.value('../trType[1]', 'nvarchar(100)') ='OldValues'
select
ID = identity(int,1,1),
T.N.value('local-name(.)', 'nvarchar(100)') as NodeName,
T.N.value('../ID[1]','nvarchar(100)') AS Table_ID,
T.N.value('.', 'nvarchar(100)') as NewValue
into #new
from @x.nodes('//UpdatedColumns/*') as T(N)
WHERE T.N.value('../trType[1]', 'nvarchar(100)') ='NewValues'
SELECT n.table_ID, n.NodeName, o.OldValue, n.NewValue,@timestamp as transation_time FROM #new n
left outer JOIN #old o ON n.NodeName = o.NodeName AND n.ID = o.ID
WHERE isnull(o.[OldValue],'') <> isnull(n.[newValue],'') AND n.NodeName <> 'trType'
DROP TABLE #new,#old
GO
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.