[英]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.