How To Create Generic SQL Server Stored Procedure To Perform Inserts Into Audit Table Based on Inserted and Deleted In Trigger

前端 未结 2 1446
难免孤独
难免孤独 2020-12-17 03:50

I have implemented an audit trail framework based on the information provided by the first answer to the following post:

SQL Server history table - populate through

相关标签:
2条回答
  • 2020-12-17 04:49

    We've solved that problem in the following way.

    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
    

    Related reading:

    • COLUMNS_UPDATED
    • sys.columns
    0 讨论(0)
  • 2020-12-17 04:49

    Ran into a similar problem... figured it out this way... may not be the most elegant solution but works for the compliance guys... So here goes...

    Based on the solution given here

    The xml is extracted with FOR XML from the trigger that updated the table... The "OldValues" come from the DELETED table and the "NewValues" from the INSERTED table... so the final xml looks like this...

                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
    
    0 讨论(0)
提交回复
热议问题