Please start any new threads on our new
site at https://forums.sqlteam.com. We've got lots of great SQL Server
experts to answer whatever question you can come up with.
Author |
Topic |
deadtrees
Starting Member
26 Posts |
Posted - 2012-03-02 : 14:37:06
|
I've been exploring triggers to audit data on my db. I found some great code, the problem is, right now in my app, the vba that triggers the writes allows me to set the pk. I set the PK as the foreign key for all but the main table because I link my audit table to the app using the PK on the main table, so people can see who made what changes. Because I set the audit to see the PK as the FK on many fields, this is possible.Therefore, I'd like to be able to manually define the primary key This is especially important because I have a junction table with two foreign keys.GO/****** Object: Trigger [dbo].[tr_audit] Script Date: 02/28/2012 08:02:05 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOALTER trigger [dbo].[tr_audit] on [dbo].[CaseDetails] for insert, update, deleteasdeclare @bit int , @field int , @maxfield int , @char int , @fieldname varchar(128) , @TableName varchar(128) , @PKCols varchar(1000) , @sql varchar(2000), @UpdateDate varchar(21) , @UserName varchar(128) , @Type char(1) , @PKSelect varchar(1000) select @TableName = 'CaseDetails' -- date and user select @UserName = CURRENT_USER,-- system_user , @UpdateDate = convert(varchar(8), getdate(), 112) + ' ' + convert(varchar(12), getdate(), 114) -- Action if exists (select * from inserted) if exists (select * from deleted) select @Type = 'U' else select @Type = 'I' else select @Type = 'D' -- get list of columns select * into #ins from inserted select * into #del from deleted -- Get primary key columns for full outer join select @PKCols = coalesce(@PKCols + ' and', ' on') + ' i.' + c.COLUMN_NAME + ' = d.' + c.COLUMN_NAME from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk , INFORMATION_SCHEMA.KEY_COLUMN_USAGE c where pk.TABLE_NAME = @TableName and CONSTRAINT_TYPE = 'PRIMARY KEY' and c.TABLE_NAME = pk.TABLE_NAME and c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME -- Get primary key select for insert select @PKSelect = coalesce(@PKSelect+'+','') + '''<' + COLUMN_NAME + '=''+convert(varchar(100),coalesce(i.' + COLUMN_NAME +',d.' + COLUMN_NAME + '))+''>''' from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk , INFORMATION_SCHEMA.KEY_COLUMN_USAGE c where pk.TABLE_NAME = @TableName and CONSTRAINT_TYPE = 'PRIMARY KEY' and c.TABLE_NAME = pk.TABLE_NAME and c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME if @PKCols is null begin raiserror('no PK on table %s', 16, -1, @TableName) return end select @field = 0, @maxfield = max(ORDINAL_POSITION) from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = @TableName while @field < @maxfield begin select @field = min(ORDINAL_POSITION) from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = @TableName and ORDINAL_POSITION > @field select @bit = (@field - 1 )% 8 + 1 select @bit = power(2,@bit - 1) select @char = ((@field - 1) / 8) + 1 if substring(COLUMNS_UPDATED(),@char, 1) & @bit > 0 or @Type in ('I','D') begin select @fieldname = COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = @TableName and ORDINAL_POSITION = @field select @sql = 'insert Audit (Type, TableName, PK, FieldName, OldValue, NewValue, UpdateDate, UserName)' select @sql = @sql + ' select ''' + @Type + '''' select @sql = @sql + ',''' + @TableName + '''' select @sql = @sql + ',' + @PKSelect select @sql = @sql + ',''' + @fieldname + '''' select @sql = @sql + ',convert(varchar(1000),d.' + @fieldname + ')' select @sql = @sql + ',convert(varchar(1000),i.' + @fieldname + ')' select @sql = @sql + ',''' + @UpdateDate + '''' select @sql = @sql + ',''' + @UserName + '''' select @sql = @sql + ' from #ins i full outer join #del d' select @sql = @sql + @PKCols select @sql = @sql + ' where i.' + @fieldname + ' <> d.' + @fieldname select @sql = @sql + ' or (i.' + @fieldname + ' is null and d.' + @fieldname + ' is not null)' select @sql = @sql + ' or (i.' + @fieldname + ' is not null and d.' + @fieldname + ' is null)' exec (@sql) end end |
|
Vinnie881
Master Smack Fu Yak Hacker
1231 Posts |
Posted - 2012-03-02 : 15:50:32
|
There is a lot I don't like about this, but here is a audit approach where it creates a separate audit table and stores every change. I personally would not use dynamic sql, but here is one way to do it. I haven't tested.ALTER trigger [dbo].[tr_audit] on [dbo].[CaseDetails] for insert, update, deleteasdeclare @sql varchar(2000), @UpdateDate varchar(21) , @UserName varchar(128) , @Type char(1) , @TableNameaudit varchar(128) , select @TableName = 'CaseDetails' set @TableNameaudit = 'CaseDetails' + '_AUDIT' -- date and user select @UserName = CURRENT_USER,-- system_user , @UpdateDate = convert(varchar(8), getdate(), 112) + ' ' + convert(varchar(12), getdate(), 114) -- Actionif exists (select * from inserted)begin if exists (select * from deleted) begin select @Type = 'U' end else begin select @Type = 'I' endendelsebegin select @Type = 'D'end -- get list of columns select * into #ins from inserted select * into #del from deletedIF not EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES where TABLE_NAME = @Tablenameaudit)beginset sql = ' select ''' + @type + ''' as type, ''' + @UserName + ''' as username, ''' + @UpdateDate + ''' as updatedate,* into ' + @TableNameaudit + ' from #ins Union all select ''' + @type + ''', ''' + @UserName + ''',''' + @UpdateDate + ''',* from #del'exec(sql)endelsebeginset sql = 'insert into @TableNameaudit select ''' + @type + ''', ''' + @UserName + ''', ''' + @UpdateDate + ''',* from #ins Union all select ''' + @type + ''', ''' + @UserName + ''',''' + @UpdateDate + ''',* from #del'exec(sql)end Success is 10% Intelligence, 70% Determination, and 22% Stupidity.\_/ _/ _/\_/ _/\_/ _/ _/- 881 |
 |
|
deadtrees
Starting Member
26 Posts |
Posted - 2012-03-05 : 12:46:24
|
Can you tell me what you don't like? I'd rather do it right in my ignorance, than figure out down the road when I'm a little more seasoned that this approach was all wrong from the getgo. |
 |
|
X002548
Not Just a Number
15586 Posts |
|
Vinnie881
Master Smack Fu Yak Hacker
1231 Posts |
Posted - 2012-03-05 : 13:16:13
|
It appears that you are using dynamic sql to save yourself coding time for each table. It is more efficient for the processor if you spell out exactly what fields and how you want them to be treated for tables you are auditing, and it does not appear that dynamic triggers are the correct approach in my opinion since you are using them so you can import into a single audit table. Second, I guess no approach is wrong if it works for you, I just personally prefer on a audit table to look at the entire record change rather than the field change. Since keeping a history table that mimics the live table is basically storing all changes that happen to the records with the same field names/types/etc as the source table, it allows you to very easily identify any changes, and in my scenarios I can easily use existing queries on audit tables just by changing the table name to append _audit, making it very easy to make simple modifications to existing queries to utilize complicated queries.If I was doing it and needed audit tables, and wanted full visibility of all changes that occur by a user, I would personally create history tables that encompassed the same structure as their source table which would ensure the record changes are stored properly, but that is a personal opinion and does not mean it is correct. Using a single table to house multiple database tables, types, etc in one location is not the approach I would use. Success is 10% Intelligence, 70% Determination, and 22% Stupidity.\_/ _/ _/\_/ _/\_/ _/ _/- 881 |
 |
|
X002548
Not Just a Number
15586 Posts |
|
deadtrees
Starting Member
26 Posts |
Posted - 2012-03-05 : 14:35:44
|
Thanks for the lesson. I suppose it's time I read further on contention and the alternate method you describe. Thanks for the info! |
 |
|
|
|
|
|
|