Author |
Topic |
im1dermike
Posting Yak Master
222 Posts |
Posted - 2008-10-27 : 12:15:35
|
I need to create a table that logs changes (updates) made in 5 other tables. The data I need to track includes:
- user who made change
- table that was changed
- column changed
- original value
- new value
I've never used triggers before, but I believe I have to create a series of triggers. I'm trying to read about triggers, but I'm having trouble wrapping my head around them. Can someone give me a starting place? |
|
visakh16
Very Important crosS Applying yaK Herder
52326 Posts |
Posted - 2008-10-27 : 12:37:33
|
you need to create a series of triggers one for each table. it will be like
CREATE TRIGGER yourtrigger ON yourtable AFTER INSERT,UPDATE AS BEGIN INSERT INTO Log_Table (table_name,user_name,modified_date,column_changed,original_value,newvalue) SELECT 'YourTable', SUSER_SNAME(), GETDATE(), CASE WHEN i.col1<>COALESCE(d.col1.'') THEN 'col1' ELSE NULL END, d.col1,i.col1,CASE WHEN i.col2<>COALESCE(d.col2.'') THEN 'col2' ELSE NULL END, d.col2,i.col2 ... FROM INSERTED 1 LEFT JOIN DELETED d ON i.PK=d.PK END PK is primary key |
 |
|
im1dermike
Posting Yak Master
222 Posts |
Posted - 2008-10-27 : 13:56:31
|
Thanks, visakh16.
I understand most of what you entered. My only issue is your code that gets the original and new values. What exactly are you doing there? Do you have a static case statement for each column to determine which columns' original and new values are different? Can you delve into that part of your example a little more and/or give another example?
Thanks |
 |
|
visakh16
Very Important crosS Applying yaK Herder
52326 Posts |
Posted - 2008-10-27 : 14:02:33
|
quote: Originally posted by im1dermike
Thanks, visakh16.
I understand most of what you entered. My only issue is your code that gets the original and new values. What exactly are you doing there? Do you have a static case statement for each column to determine which columns' original and new values are different? Can you delve into that part of your example a little more and/or give another example?
Thanks
the case statements are to return the column names which changed and NULL if no change. There's no direct way to get column name of column which changed in trigger code. thats why i have given static case statements for each column in table. original and new values are obtained from INSERTED & DELETED internal temporary tables for each column. |
 |
|
im1dermike
Posting Yak Master
222 Posts |
Posted - 2008-10-27 : 14:11:33
|
So if I understand you correctly, you're returning a string of column names which changed? I believe what I need is a row for each change meaning that if the value of two fields in a table was changed, two records would then appear in the log table, each with the value and column reference of a single change.
Is this possible or do you have to have any/all changes in a single record? |
 |
|
blindman
Master Smack Fu Yak Hacker
2365 Posts |
Posted - 2008-10-27 : 15:19:44
|
1dermike, why are you not just using a archive tables to record all historical record values? The method you appear to be using in neither the easiest to code, nor the easiest to query.
If it is not practically useful, then it is practically useless. |
 |
|
im1dermike
Posting Yak Master
222 Posts |
Posted - 2008-10-27 : 15:45:50
|
blindman: Never heard of archive tables. Can you provide me with some info on them please? A google search of "sql archive tables" didn't seem to return things that were as fruitful as I expected them to be. |
 |
|
blindman
Master Smack Fu Yak Hacker
2365 Posts |
Posted - 2008-10-28 : 10:11:26
|
An archive table is identical in design to the production table, with the addition of a time stamp column, a flag indicating whether the record was deleted, and possibly a column for recording who modified the data. It stores a complete history of all the records in the production table, including the current record. Triggers on the production tables copie all inserts, update, and deletes to the archive tables. Most queries can be run directly against the production table, but point-in-time queries can be run against the archive tables, and of course they can also be used for auditing.
I have a script that will generate archive tables and triggers for each production table in your database, if you are interested.
If it is not practically useful, then it is practically useless. |
 |
|
im1dermike
Posting Yak Master
222 Posts |
Posted - 2008-10-29 : 14:58:32
|
Yes, that would be great. |
 |
|
Lamprey
Master Smack Fu Yak Hacker
4614 Posts |
Posted - 2008-10-29 : 18:01:50
|
I assume you are not using Sql 2008, but if you are, then you could make use of the Change Data Capture functionality. Or maybe lookas how they do it in 2008 and apply some of that to your history tables. |
 |
|
blindman
Master Smack Fu Yak Hacker
2365 Posts |
Posted - 2008-10-30 : 09:27:39
|
1dermike, here you go....
set nocount on --Auto archive script --blindman, 3/2/2006 --blindman, 11/7/2007: Enhanced to handle inserts/updates/deletes
--creates a script to create archive tables and triggers for saving deleted records.
declare @TableNameMask varchar(50) declare @ArchiveSuffix varchar(50) declare @DeletedColumnName varchar(50) set @TableNameMask = '' --Filters specific tables by name. set @ArchiveSuffix = '_arc' --Suffix to append to table names. set @DeletedColumnName = 'DeletedFlag' --Will indicate whether archive record was the result of a delete operation.
declare @TableName varchar(500) declare @FieldDefs varchar(max) declare @FieldList varchar(max) declare @DeletedList varchar(max) declare @SQLStringTABLECREATE varchar(max) declare @SQLStringTRIGGERHEADER varchar(max) declare @SQLStringTRIGGERINSERT varchar(max) declare @SQLStringTRIGGERDELETE varchar(max) declare @KeyList varchar(max) declare @FirstKey varchar(100)
/* select '/*' select name as 'The following tables will be scripted:' from sysobjects where type = 'U' and name not like 'dt%' and name not like '%' + @ArchiveSuffix and name not like '%staging%' order by name select '*/' */
declare TableList cursor for select name from sysobjects where type = 'U' and (name like @TableNameMask or nullif(@TableNameMask, '') is null) and name not like 'dt%' and name not like '%' + @ArchiveSuffix and name not like '%staging%' and name not in ('sysdiagrams') order by name
open TableList fetch next from TableList into @TableName
while @@fetch_status = 0 begin set @FieldDefs = null set @FieldList = null set @DeletedList = null set @SQLStringTABLECREATE = null set @SQLStringTRIGGERHEADER = null set @SQLStringTRIGGERINSERT = null set @SQLStringTRIGGERDELETE = null set @KeyList = null set @FirstKey = null
select @FieldDefs = isnull(@FieldDefs + ',' +char(10) + ' ', '') + syscolumns.name + ' ' + systypes.name + isnull(' (' + case systypes.name when 'timestamp' then Null when 'smalldatetime' then Null when 'datetime' then Null when 'int' then Null when 'bigint' then Null when 'smallint' then Null when 'tinyint' then Null when 'real' then Null when 'bit' then Null when 'money' then Null when 'uniqueidentifier' then Null when 'xml' then Null when 'decimal' then cast(syscolumns.prec as varchar(10)) + ', ' + cast(syscolumns.scale as varchar(10)) when 'numeric' then cast(syscolumns.prec as varchar(10)) + ', ' + cast(syscolumns.scale as varchar(10)) else case when syscolumns.length = -1 then 'max' else cast(syscolumns.length as varchar(10)) end end + ')', '') + ' ' + case syscolumns.isnullable when 0 then 'NOT NULL' else 'NULL' end from syscolumns inner join sysobjects on syscolumns.id = sysobjects.id inner join systypes on syscolumns.xusertype = systypes.xusertype where sysobjects.name = @TableName and systypes.name not in ('text', 'ntext', 'image') --These cannot be referenced in the deleted table and syscolumns.name not in ('Modified', 'Modifier') order by syscolumns.colid
select @FieldList = isnull(@FieldList + ',' +char(10) + ' ', '') + syscolumns.name from syscolumns inner join sysobjects on syscolumns.id = sysobjects.id inner join systypes on syscolumns.xusertype = systypes.xusertype where sysobjects.name = @TableName and systypes.name not in ('text', 'ntext', 'image') --These cannot be referenced in the deleted table and syscolumns.name not in ('Modified', 'Modifier') order by syscolumns.colid
select @DeletedList = isnull(@DeletedList + ',' +char(10) + ' ', '') + 'deleted.' + syscolumns.name from syscolumns inner join sysobjects on syscolumns.id = sysobjects.id inner join systypes on syscolumns.xusertype = systypes.xusertype where sysobjects.name = @TableName and systypes.name not in ('text', 'ntext', 'image') --These cannot be referenced in the deleted table and syscolumns.name not in ('Modified', 'Modifier') order by syscolumns.colid
select @KeyList = isnull(@KeyList + char(10) + ' and ', '') + 'deleted.' + columns.name + ' = inserted.' + columns.name from sys.objects objects inner join sys.indexes indexes on objects.object_id = indexes.object_id inner join sys.index_columns index_columns on indexes.object_id = index_columns.object_id and indexes.index_id = index_columns.index_id inner join sys.columns columns on index_columns.object_id = columns.object_id and index_columns.column_id = columns.column_id where is_primary_key = 1 and objects.name = @TableName
set @FirstKey = (select top 1 columns.name from sys.objects objects inner join sys.indexes indexes on objects.object_id = indexes.object_id inner join sys.index_columns index_columns on indexes.object_id = index_columns.object_id and indexes.index_id = index_columns.index_id inner join sys.columns columns on index_columns.object_id = columns.object_id and index_columns.column_id = columns.column_id where is_primary_key = 1 and objects.name = @TableName order by columns.column_id)
set @SQLStringTABLECREATE = '--<<[' + @TableName + ']>>--' + char(10) + 'Create table [' + @TableName + @ArchiveSuffix + ']' + char(10) + ' (' + @FieldDefs + ',' + char(10) + ' ' + @DeletedColumnName + ' bit NOT NULL,' + char(10) + ' Modified datetime NULL,' + char(10) + ' Modifier varchar (50) NULL)' + char(10) + 'GO' + char(10) + char(10) + 'ALTER TABLE [' + @TableName + @ArchiveSuffix + '] ADD ' + char(10) + ' CONSTRAINT [DF_' + @TableName + @ArchiveSuffix + '_Deleted] DEFAULT (0) FOR [' + @DeletedColumnName + '],' + char(10) + ' CONSTRAINT [DF_' + @TableName + @ArchiveSuffix + '_Modified] DEFAULT (getdate()) FOR [Modified],' + char(10) + ' CONSTRAINT [DF_' + @TableName + @ArchiveSuffix + '_Modifier] DEFAULT (suser_sname()) FOR [Modifier]' + char(10) + 'GO' + char(10)
set @SQLStringTRIGGERHEADER ='CREATE TRIGGER [TR_' + @TableName + '_Archiver] ON [' + @TableName + ']' + char(10) + 'FOR INSERT, UPDATE, DELETE' + char(10) + 'AS' + char(10) + 'set nocount on' + char(10) + 'if exists (select * from dbo.sysobjects where id = object_id(N''[' + @TableName + @ArchiveSuffix + ']'') and OBJECTPROPERTY(id, N''IsUserTable'') = 1)' + char(10) + 'begin'
set @SQLStringTRIGGERINSERT = 'insert into [' + @TableName + @ArchiveSuffix + ']' + char(10) + ' (' + @FieldList + ',' + char(10) + ' ' + @DeletedColumnName + ')' + char(10) + 'select ' + @FieldList + ',' + char(10) + ' 0' + char(10) + 'from inserted' + char(10) + 'where TRIGGER_NESTLEVEL() = 1' + char(10)
set @SQLStringTRIGGERDELETE = 'insert into [' + @TableName + @ArchiveSuffix + ']' + char(10) + ' (' + @FieldList + ',' + char(10) + ' ' + @DeletedColumnName + ')' + char(10) + 'select ' + @DeletedList + ',' + char(10) + ' 1' + char(10) + 'from deleted' + char(10) + 'left outer join inserted on ' + @KeyList + char(10) + 'where inserted.' + @FirstKey + ' is null' + char(10) + 'end' + char(10) + 'GO'
if @FirstKey is not null begin print @SQLStringTABLECREATE print @SQLStringTRIGGERHEADER print @SQLStringTRIGGERINSERT print @SQLStringTRIGGERDELETE end else select '--NO PRIMARY KEY ON TABLE ' + @TableName + '!'
fetch next from TableList into @TableName end
close tablelist deallocate tablelist
If it is not practically useful, then it is practically useless. |
 |
|
im1dermike
Posting Yak Master
222 Posts |
Posted - 2008-11-10 : 10:14:46
|
Thanks blindman. So all I need to do is alter the TableList query to select only the tables I'd like to archive? Do I need to create the _arc tables before too? |
 |
|
blindman
Master Smack Fu Yak Hacker
2365 Posts |
Posted - 2008-11-10 : 10:37:47
|
Yes, and it creates a script that you can edit before executing if you want to customize it.
If it is not practically useful, then it is practically useless. |
 |
|
im1dermike
Posting Yak Master
222 Posts |
Posted - 2008-11-10 : 15:12:15
|
Do my tables have to have primary keys because they don't. |
 |
|
im1dermike
Posting Yak Master
222 Posts |
Posted - 2008-11-10 : 15:21:55
|
Also, is there any way to have the archive tables in a different, centralized database than the one where the tables are located? |
 |
|
blindman
Master Smack Fu Yak Hacker
2365 Posts |
Posted - 2008-11-10 : 20:45:49
|
Yes. You need a unique key on each table. If you don't have that, you have real problems.
No, you should not use triggers to communicate between databases. Bad idea.
If it is not practically useful, then it is practically useless. |
 |
|
im1dermike
Posting Yak Master
222 Posts |
Posted - 2008-11-13 : 10:55:33
|
Well, yes I would have real problems with regard to your functionality, but surely tables can exist that don't have a primary key.
I think what I'm just going to have to do is create a stored procedure and call it whenever changes are made. They changes I'm trying to track are made via an ASP website I've created. This will also allow me to store the changes in a centralized database.
Thanks for your help |
 |
|
blindman
Master Smack Fu Yak Hacker
2365 Posts |
Posted - 2008-11-14 : 09:42:15
|
quote: Originally posted by im1dermike
Well, yes I would have real problems with regard to your functionality, but surely tables can exist that don't have a primary key.
Of course. And you can have a car without brakes as well. Just let me know when you are going to be on the road so I can plan accordingly.
If it is not practically useful, then it is practically useless. |
 |
|
im1dermike
Posting Yak Master
222 Posts |
Posted - 2008-11-14 : 09:48:15
|
Can you explain to me why every table should have a primary key? |
 |
|
blindman
Master Smack Fu Yak Hacker
2365 Posts |
Posted - 2008-11-14 : 11:15:09
|
Every table should have a method to uniquely identify each record. That is just database design 101. Otherwise, you cannot issue statements against it with predictable results.
If it is not practically useful, then it is practically useless. |
 |
|
im1dermike
Posting Yak Master
222 Posts |
Posted - 2008-11-14 : 11:16:59
|
Obviously there shouldn't be any duplicate records, but a table doesn't need a primary key. |
 |
|
Next Page
|