簡體   English   中英

如何創建通用SQL Server存儲過程以基於插入和刪除的觸發器執行插入審計表

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM