T-SQL script automatically adds auditing to a table

The script below looks at the structure of the table you specify and creates a new table called xxx_Audit with the same structure plus a timestamp, less any constraints/unique keys etc.

It then creates a new trigger on the original table to populate the xxx_Audit table.

It doesn't cope with DELETE statements, but then I don't normally allow application users to actually delete data (I have a deleted 'bit' column and whatever purge criteria is appropriate to the application).

You could extend the TRIGGER to handle deletes also without much difficulty.

The SQL file is attached, feel free to use it...

DECLARE @TABLENAME varchar(100)
DECLARE @SCHEMA varchar(100)
SET @TABLENAME = N'yourTableName'
SET @SCHEMA = N'dbo'

DECLARE @Done bit 
SET @Done=0 

DECLARE @CRLF char(2)
SET @CRLF = char(10)
DECLARE @SQL varchar(1000)
SET @SQL = '
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N''['+@SCHEMA+'].
['+@TABLENAME+'_Audit]'') AND type in (N''U''))
DROP TABLE [dbo].['+@TABLENAME+'_Audit]
CREATE TABLE ['+@SCHEMA+'].['+@TABLENAME+'_Audit] ('+@CRLF
DECLARE @COLUMNID int
SET @COLUMNID = 0
DECLARE @COLUMNNAME varchar(1000)
DECLARE @COLUMNTYPE varchar(100)
DECLARE @COLUMNSIZE int

WHILE @Done=0   
BEGIN            
    SELECT top 1
    @COLUMNID=clmns.column_id,
    @COLUMNNAME=clmns.name ,
    @COLUMNTYPE=usrt.name ,
    @COLUMNSIZE=CAST(CASE WHEN baset.name IN (N'nchar', N'nvarchar'AND 
clmns.max_length <> -1 THEN clmns.max_length/2 ELSE clmns.max_length END AS int)
    FROM
    sys.tables AS tbl
    INNER JOIN sys.all_columns AS clmns ON clmns.object_id=tbl.object_id
    LEFT OUTER JOIN sys.types AS usrt ON usrt.user_type_id = clmns.user_type_id
    LEFT OUTER JOIN sys.types AS baset ON baset.user_type_id = clmns.system_type_id and 
baset.user_type_id = baset.system_type_id
    WHERE
    (tbl.name=@TABLENAME and SCHEMA_NAME(tbl.schema_id)=@SCHEMA)
    and clmns.column_id > @COLUMNID
    ORDER BY
    clmns.column_id asc

    IF @@rowcount=0  
        begin   
            SET @Done=1  
        end
    else
        begin
            SET @SQL=@SQL+'['+@COLUMNNAME+'] ['+@COLUMNTYPE+'] '
            IF (@COLUMNTYPE='varchar'SET @SQL=@SQL+'('+ltrim(str(@COLUMNSIZE))
+') '
            SET @SQL=@SQL+'NULL, '+@CRLF
        end
END

SET @SQL=@SQL+'[timestamp] datetime )'
print @SQL
exec (@SQL)
SET @SQL='IF  EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N''[dbo].
['+@TABLENAME+'_Trigger]''))
DROP TRIGGER [dbo].['+@TABLENAME+'_Trigger]'
print @SQL
exec(@SQL)

SET @SQL='CREATE TRIGGER ['+@TABLENAME+'_Trigger] 
   ON  ['+@SCHEMA+'].['+@TABLENAME+'] 
   AFTER UPDATE, INSERT
AS 
BEGIN
    SET NOCOUNT ON;
    INSERT INTO '+@TABLENAME+'_Audit 
    SELECT *,getdate() FROM inserted
END'
print @SQL
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
exec (@SQL)

Author Dave Howard

I have been involved in IT development for the last 10 years - a lot of it around desktop applications and telecoms.

Comments

Anon said:

I seem to get this when tyring to use your script. Please advise.

Server: Msg 195, Level 15, State 10, Line 35

'SCHEMA_NAME' is not a recognized function name.

21/Sep/2006 15:30 PM

KevinM said:

Same error as Anon

19/Feb/2007 15:52 PM

Andrew said:

I keeping having issues with the column size of the audit table always being set to 1 - Any ideas?

30/May/2007 19:57 PM

2005 said:

Are you using server 2005?

11/Sep/2007 19:20 PM

kkemerait said:

Same - No SCHEMA_NAME Function

07/Feb/2008 14:36 PM

Travis said:

I created an SProc based on the above script that handles INSERT, UPDATE and DELETE. The audit table will have a complete history of each record, including the current record in the primary/trigger table.

I use an 'UpdateAction' field in the Audit table to capture the action.

I would highly recommend to anybody using this script to include a CreateDate and CreateUser field in the primary/trigger table. That will allow you to capture who made what changes.

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

-- =============================================

-- Author: <Author,,Name>

-- Create date: <Create Date,,>

-- Description: <Description,,>

-- =============================================

ALTER PROCEDURE dbo.spCreateAuditTable

        -- Add the parameters for the stored procedure here
        @TableName nvarchar(100)

AS

BEGIN

        -- SET NOCOUNT ON added to prevent extra result sets from
        -- interfering with SELECT statements.
        SET NOCOUNT ON;


    --DECLARE @TABLENAME varchar(100)
        DECLARE @SCHEMA varchar(100)
        --SET @TABLENAME = N'Company'
        SET @SCHEMA = N'dbo'


        DECLARE @Done bit 
        SET @Done=0 


        DECLARE @CRLF char(2)
        SET @CRLF = char(10)
        DECLARE @SQL varchar(2000)
        SET @SQL = 'IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N''['+@SCHEMA+'].['+@TABLENAME+'_Audit]'') AND type in (N''U''))
        DROP TABLE [dbo].['+@TABLENAME+'_Audit]

CREATE TABLE ['@SCHEMA'].['@TABLENAME'_Audit] ('+@CRLF

        DECLARE @COLUMNID int
        SET @COLUMNID = 0
        DECLARE @COLUMNNAME varchar(1000)
        DECLARE @COLUMNTYPE varchar(100)
        DECLARE @COLUMNSIZE int


        WHILE @Done=0   
        BEGIN            
                SELECT top 1
                @COLUMNID=clmns.column_id,
                @COLUMNNAME=clmns.name ,
                @COLUMNTYPE=usrt.name ,
                @COLUMNSIZE=CAST(CASE WHEN baset.name IN (N'nchar', N'nvarchar') AND 
        clmns.max_length <> -1 THEN clmns.max_length/2 ELSE clmns.max_length END AS int)
                FROM
                sys.tables AS tbl
                INNER JOIN sys.all_columns AS clmns ON clmns.object_id=tbl.object_id
                LEFT OUTER JOIN sys.types AS usrt ON usrt.user_type_id = clmns.user_type_id
                LEFT OUTER JOIN sys.types AS baset ON baset.user_type_id = clmns.system_type_id and 
        baset.user_type_id = baset.system_type_id
                WHERE
                (tbl.name=@TABLENAME and SCHEMA_NAME(tbl.schema_id)=@SCHEMA)
                and clmns.column_id > @COLUMNID
                ORDER BY
                clmns.column_id asc


                IF @@rowcount=0  
                        begin   
                                SET @Done=1  
                        end
                else
                        begin
                                SET @SQL=@SQL+'        ['+@COLUMNNAME+'] ['+@COLUMNTYPE+'] '
                                IF (@COLUMNTYPE='nchar' OR @COLUMNTYPE='nvarchar' OR @COLUMNTYPE='varchar') SET @SQL=@SQL+'('+ltrim(str(@COLUMNSIZE))
        +') '
                                SET @SQL=@SQL+'NULL, '+@CRLF
                        end
        END


        SET @SQL=@SQL+'        ['+@TABLENAME+'UpdateDate] datetime,
        ['+@TABLENAME+'UpdateAction] nvarchar(10) )

'

        --print @SQL
        exec (@SQL)


        SET @SQL='IF  EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N''[dbo].['+@TABLENAME+'_Trigger_Update]''))
        DROP TRIGGER [dbo].['+@TABLENAME+'_Trigger_Update]

IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N[dbo].['@TABLENAME'TriggerDelete]))

        DROP TRIGGER [dbo].['+@TABLENAME+'_Trigger_Delete]

IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N[dbo].['@TABLENAME'TriggerInsert]))

        DROP TRIGGER [dbo].['+@TABLENAME+'_Trigger_Insert]

'

        --print @SQL
        exec(@SQL)


        SET @SQL='CREATE TRIGGER ['+@TABLENAME+'_Trigger_Update] 
   ON  ['+@SCHEMA+'].['+@TABLENAME+'] 
   AFTER UPDATE

AS

BEGIN

        SET NOCOUNT ON;
        INSERT INTO '+@TABLENAME+'_Audit 
        SELECT *,getdate(),''Update'' FROM inserted

END

'

        exec (@SQL)
        --print @SQL


        SET @SQL='CREATE TRIGGER ['+@TABLENAME+'_Trigger_Delete] 
   ON  ['+@SCHEMA+'].['+@TABLENAME+'] 
   AFTER DELETE

AS

BEGIN

        SET NOCOUNT ON;
        INSERT INTO '+@TABLENAME+'_Audit 
        SELECT *,getdate(),''Delete'' FROM deleted

END

'

        exec (@SQL)
        --print @SQL


        SET @SQL='CREATE TRIGGER ['+@TABLENAME+'_Trigger_Insert] 
   ON  ['+@SCHEMA+'].['+@TABLENAME+'] 
   AFTER INSERT

AS

BEGIN

        SET NOCOUNT ON;
        INSERT INTO '+@TABLENAME+'_Audit 
        SELECT *,getdate(),''Insert'' FROM inserted

END

'

        exec (@SQL)
        --print @SQL

END

18/Mar/2010 22:20 PM

Krishna said:

Msg 105, Level 15, State 1, Line 30

Unclosed quotation mark after the character string 'con'.

Msg 102, Level 15, State 1, Line 30

Incorrect syntax near 'con'.

This is the error i'm getting

26/May/2010 07:50 AM

Add Comment

Name
Comment
 

Your comment has been received and will be shown once it passes moderation.