Log changes to database table with trigger

前端 未结 12 2290
醉梦人生
醉梦人生 2021-02-08 18:48

I am looking for a good way to log changes that occur on a particular set of tables in my SQL Server 2005 database. I believe the best way to do this is through a trigger that g

12条回答
  •  后悔当初
    2021-02-08 19:29

    This is adapted from Juan Carlos Velez's answer. I modified it to account for compound primary keys, and for column names that include spaces. Also, I commented it throughout so that someone who wants to modify it for their purposes can understand what is happening at each step, if the code is not clear to them.

    -- This stops the message that shows the count of the number of rows affected from being returned as part of the result set.
    set nocount on
    
    -- If the Audit table doesn't exist, create it.
    if not exists(select * from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'Audit')
    create table Audit
    (
        AuditID [int] identity(1,1) not null,
        [Type] char(1), 
        TableName nvarchar(128), 
        PKFields nvarchar(max),
        PKValues nvarchar(max),
        FieldName nvarchar(128), 
        OldValue nvarchar(max), 
        NewValue nvarchar(max), 
        UpdateDate datetime, 
        UserName nvarchar(128)
    )
    go
    
    -- Variables for the dynamic SQL and table name.
    declare @tr nvarchar(max),
    @tableName sysname
    
    -- Get the first table in database. Skip over views and a few specified tables.
    select @tableName = min(TABLE_NAME) from INFORMATION_SCHEMA.TABLES where TABLE_TYPE = 'BASE TABLE' and TABLE_NAME <> 'sysdiagrams' and TABLE_NAME <> 'Audit'
    ---- If you want to specify certain tables, uncomment the next line and add your table names.
    --and (TABLE_NAME = 'IGAs' or TABLE_NAME = 'IGABudgets' or TABLE_NAME = 'Resolutions' or TABLE_NAME = 'RTAProjects' or TABLE_NAME = 'RTASubProjects')
    
    -- Loop through the tables in the database and create an audit trigger on each one.
    while @tableName is not null
    begin
    
        -- If a trigger of the same name already exists, delete it.
        exec('if OBJECT_ID (''' + @tableName + '_ChangeTracking'', ''TR'') is not null drop trigger ' + @tableName + '_ChangeTracking')
    
        -- Check if there is a primary key. If not, throw an error.
        if (select count(*) from INFORMATION_SCHEMA.TABLE_CONSTRAINTS c, INFORMATION_SCHEMA.KEY_COLUMN_USAGE u where c.TABLE_NAME = @tableName and c.CONSTRAINT_TYPE = 'PRIMARY KEY' and u.TABLE_NAME = c.TABLE_NAME and u.CONSTRAINT_NAME = c.CONSTRAINT_NAME) = 0
        begin
            raiserror('Error: There is no primary key on table %s', 16, -1, @tableName)
            return
        end
    
        -- Create the trigger.
        select @tr = 'create trigger ' + @tableName + '_ChangeTracking on ' + @tableName + ' for insert, update, delete as
    
        -- Misc variables.
        declare @table nvarchar(128),
        @fieldName nvarchar(128) = '''',
        @type char(1),
        @pkJoin nvarchar(max),
        @pkSelect nvarchar(max),
        @pkFields nvarchar(max),
        @pkValues nvarchar(max),
        @updateDate nvarchar(30) = convert(varchar(30), getdate(), 22),
        @user nvarchar(128) = system_user,
        @sql nvarchar(max),
        @params nvarchar(max) = N''@out nvarchar(max) output'',
        @fieldIndex int = 0,
        @maxField int,
        @bit int,
        @char int
    
        -- Get the table name.
        select @table = object_name(parent_id) from sys.triggers where object_id = @@PROCID
    
        -- Get the modification type: U = update, I = insert, D = delete
        if exists (select * from inserted)
            if exists (select * from deleted)
                select @type = ''U''
            else select @type = ''I''
        else select @type = ''D''
    
        -- Save the inserted and deleted values into temp tables.
        select * into #ins from inserted
        select * into #del from deleted
    
        -- Get the number of columns in the table.
        select @maxField = max(columnproperty(object_id(@table), COLUMN_NAME, ''ColumnID'')) from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = @table
    
        -- Get the primary key join relationship(s).
        select @pkJoin = coalesce(@pkJoin + '' and'', '' on'') + '' i.['' + u.COLUMN_NAME + ''] = d.['' + u.COLUMN_NAME + '']''
        from INFORMATION_SCHEMA.TABLE_CONSTRAINTS c, INFORMATION_SCHEMA.KEY_COLUMN_USAGE u
        where c.TABLE_NAME = @table
        and c.CONSTRAINT_TYPE = ''PRIMARY KEY''
        and u.TABLE_NAME = c.TABLE_NAME
        and u.CONSTRAINT_NAME = c.CONSTRAINT_NAME
    
        -- Get the primary key field name(s).
        select @pkFields = coalesce(@pkFields + '', '', '''') + ''['' + u.COLUMN_NAME + '']''
        from INFORMATION_SCHEMA.TABLE_CONSTRAINTS c, INFORMATION_SCHEMA.KEY_COLUMN_USAGE u
        where c.TABLE_NAME = @table
        and c.CONSTRAINT_TYPE = ''PRIMARY KEY''
        and u.TABLE_NAME = c.TABLE_NAME
        and u.CONSTRAINT_NAME = c.CONSTRAINT_NAME
    
        -- Get the primary key field(s) for select statement.
        select @pkSelect = coalesce(@pkSelect + '' + '''', '''' + '', '''') + ''convert(nvarchar(max), ['' + u.COLUMN_NAME + ''])''
        from INFORMATION_SCHEMA.TABLE_CONSTRAINTS c, INFORMATION_SCHEMA.KEY_COLUMN_USAGE u
        where c.TABLE_NAME = @table
        and c.CONSTRAINT_TYPE = ''PRIMARY KEY''
        and u.TABLE_NAME = c.TABLE_NAME
        and u.CONSTRAINT_NAME = c.CONSTRAINT_NAME
    
        -- Get the primary key field value(s).
        if (@type = ''D'')
        begin
            set @sql = ''select @out = '' + @pkSelect + '' from #del''
            exec sp_executesql @sql, @params, @out = @pkValues output
        end
        else
        begin 
            set @sql = ''select @out = '' + @pkSelect + '' from #ins''
            exec sp_executesql @sql, @params, @out = @pkValues output
        end
    
        -- Loop through each field in the inserted table.
        while @fieldIndex < @maxField
        begin
    
            -- Iterate the fieldIndex.
            select @fieldIndex = min(ORDINAL_POSITION) from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = @table and columnproperty(object_id(@table), COLUMN_NAME, ''ColumnID'') > @fieldIndex 
    
            -- If the column in scope has been modified, insert a record into the Audit table.
            select @bit = (@fieldIndex - 1)% 8 + 1
            select @bit = POWER(2, @bit - 1)
            select @char = ((@fieldIndex - 1) / 8) + 1
            if substring(columns_updated(), @char, 1) & @bit > 0 or @Type IN (''I'', ''D'')
            begin
    
                -- Get the name of the field whose ColumnID equals the current fieldIndex.
                select @fieldName = ''['' + COLUMN_NAME + '']'' from INFORMATION_SCHEMA.COLUMNS 
                where TABLE_NAME = @table and columnproperty(object_id(@table), COLUMN_NAME, ''ColumnID'') = @fieldIndex '
    
        -- Select statements have a length limitation. End the statement, then add the rest.
        select @tr = @tr + '
    
                set @sql = ''insert into Audit (Type, TableName, PKFields, PKValues, FieldName, OldValue, NewValue, UpdateDate, UserName) select '''''' + @type + '''''', '''''' + @table + '''''', '''''' + @pkFields + '''''', '''''' + @pkValues + '''''', '''''' + @fieldName + '''''', convert(nvarchar(max), d.'' + @fieldName + ''), convert(nvarchar(max), i.'' + @fieldName + ''), '''''' + @updateDate + '''''', '''''' + @user + '''''' from #ins i full outer join #del d'' + @pkJoin + '' where i.'' + @fieldName + '' <> d.'' + @fieldName + '' or (i.'' + @fieldName + '' is null and d.'' + @fieldName + '' is not null) or (i.'' + @fieldName + '' is not null and d.'' + @fieldName + '' is null)''
    
                --print(@sql)
                exec(@sql)
    
            end
        end'
    
        ---- This is if you want to see the statement that is generated rather than execute it.
        --select @tr
    
        -- Execute the trigger statement.
        exec(@tr)
    
        -- Iterate the table name.
        select @tableName = min(TABLE_NAME) from INFORMATION_SCHEMA.TABLES 
        where TABLE_NAME > @tableName and
        TABLE_TYPE = 'BASE TABLE' and TABLE_NAME <> 'sysdiagrams' and TABLE_NAME <> 'Audit'
        ---- If you want to specify certain tables, uncomment the next line and add your table names.
        --and (TABLE_NAME = 'IGAs' or TABLE_NAME = 'IGABudgets' or TABLE_NAME = 'Resolutions' or TABLE_NAME = 'RTAProjects' or TABLE_NAME = 'RTASubProjects')
    
    end
    

提交回复
热议问题