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
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