SQL Server Forums
Profile | Register | Active Topics | Members | Search | Forum FAQ
 
Register Now and get your question answered!
Username:
Password:
Save Password
Forgot your Password?

 All Forums
 SQL Server 2008 Forums
 Transact-SQL (2008)
 Manually Set Primary Key in Audit Trail
 New Topic  Reply to Topic
 Printer Friendly
Author Previous Topic Topic Next Topic  

deadtrees
Starting Member

22 Posts

Posted - 03/02/2012 :  14:37:06  Show Profile  Reply with Quote
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
Flowing Fount of Yak Knowledge

USA
1203 Posts

Posted - 03/02/2012 :  15:50:32  Show Profile  Reply with Quote
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

Edited by - Vinnie881 on 03/02/2012 15:54:04
Go to Top of Page

deadtrees
Starting Member

22 Posts

Posted - 03/05/2012 :  12:46:24  Show Profile  Reply with Quote
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 - 03/05/2012 :  13:09:35  Show Profile  Reply with Quote
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
Flowing Fount of Yak Knowledge

USA
1203 Posts

Posted - 03/05/2012 :  13:16:13  Show Profile  Reply with Quote
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

Edited by - Vinnie881 on 03/05/2012 13:20:04
Go to Top of Page

X002548
Not Just a Number

15586 Posts

Posted - 03/05/2012 :  13:19:22  Show Profile  Reply with Quote
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

22 Posts

Posted - 03/05/2012 :  14:35:44  Show Profile  Reply with Quote
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
  Previous Topic Topic Next Topic  
 New Topic  Reply to Topic
 Printer Friendly
Jump To:
SQL Server Forums © 2000-2009 SQLTeam Publishing, LLC Go To Top Of Page
This page was generated in 0.12 seconds. Powered By: Snitz Forums 2000