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.

 All Forums
 SQL Server 2008 Forums
 Transact-SQL (2008)
 Manually Set Primary Key in Audit Trail

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 ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER trigger [dbo].[tr_audit] on [dbo].[CaseDetails] for insert, update, delete
as

declare @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, delete
as

declare
@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)

-- Action

if exists (select * from inserted)
begin
if exists (select * from deleted)
begin
select @Type = 'U'
end
else
begin
select @Type = 'I'
end
end
else
begin
select @Type = 'D'
end
-- get list of columns
select * into #ins from inserted
select * into #del from deleted
IF not EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES where TABLE_NAME = @Tablenameaudit)
begin
set 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)
end
else
begin
set 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
Go to Top of Page

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.

Go to Top of Page

X002548
Not Just a Number

15586 Posts

Posted - 2012-03-05 : 13:09:35
You're creating a dynamic trigger?

I use the catalog to generate the triggers for me

http://weblogs.sqlteam.com/brettk/archive/2006/08/10/11126.aspx

Brett

8-)

Hint: Want your questions answered fast? Follow the direction in this link
http://weblogs.sqlteam.com/brettk/archive/2005/05/25/5276.aspx


Want to help yourself?

http://msdn.microsoft.com/en-us/library/ms130214.aspx

http://weblogs.sqlteam.com/brettk/

http://brettkaiser.blogspot.com/


Go to Top of Page

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
Go to Top of Page

X002548
Not Just a Number

15586 Posts

Posted - 2012-03-05 : 13:19:22
And the reason is contention...

Your triggers should be lightweight and not interfere with a transaction

you are creating a bottleneck doing this



Brett

8-)

Hint: Want your questions answered fast? Follow the direction in this link
http://weblogs.sqlteam.com/brettk/archive/2005/05/25/5276.aspx


Want to help yourself?

http://msdn.microsoft.com/en-us/library/ms130214.aspx

http://weblogs.sqlteam.com/brettk/

http://brettkaiser.blogspot.com/


Go to Top of Page

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!
Go to Top of Page
   

- Advertisement -