
USE AdventureWorks2008  -- edit for your database

/* 

AutoAudit script
Paul Nielsen
www.sqlserverbible.com


----------------------------------
version 1.00 - Jan 15, 2007

This script creates the audit table and builds 4 stored procedures. 

AutoAudit 'schema', 'table'
which: 
   1) creates dbo.audit  table if not already there
   2) adds created and modified columns to table
   3) builds insert, update, and modified audit trail trigger

AutoAditDrop 'schema', 'table'
which: 
1)	drops insert, update, and modified audit trail trigger

AutoAuditAll  
AuditDropAll
1)	runs the autoaudit or autoauditdrop for every table in the database other than the audit table. 

This is only a version 1, so test before you put it in production. 
Im open to suggestions and requests. 

This version is limited to tables with single column primary keys. 
But thats easy to change if enough readers ask for it. Also, in the next version Im going to add error handling. 

-----------------------------------
version 1.01 - Jan 15, 2007
   added row version column, incremented by the modified trigger
   cleaned up how the tablename is written to the audit.tablename column 
   added delete trigger, which just writes the table, pk, and operation ('d') to the audit table
   changed Audit.[Column] to Audit.ColumnName

		-- Query to determine lastvalues of deleted rows 
		Select TableName, PrimaryKey, ColumnName, NewValue as LastValue from dbo.audit 
		 Where Operation IN ('i', 'u') 
			AND AuditID IN (
				Select Max(AuditID) 
				  FROM dbo.audit a
					JOIN (SELECT TableName, PrimaryKey FROM dbo.audit where operation = 'd') d
					  on a.tablename = d.tablename and a.primarykey = d.primarykey
				  GROUP BY a.TableName, a.PrimaryKey, a.ColumnName )

-----------------------------------
version 1.02 - Jan 16, 2007
   fixed bug: Duplicate Columns. databases with user-defined types was causing the user-defined types to show up as system types. 
   added code gen to create [table]_deleted view that returns all deleted rows for the table

-----------------------------------
version 1.03 - Jan 16, 2007
   converted from cursor to Multiple Assignment Variable for building of for-each-column code
   added created, modifed, and delted columns to _deleted view 

-----------------------------------
version 1.04 - Jan 18, 2007
  minor clean-up on _deleted view. Removed extra Primary Key Column. 

-----------------------------------
version 1.05 - Jan 18, 2007
  changed from writing just the delete bit to writing the whole row. 
  modified _deleted view to return RowVersion

-----------------------------------
version 1.06 - Jan 30, 2007
  added host_name to audit trail
  improved modifed trigger run-away recursive trigger detection
  added basic error-trapping

-----------------------------------
version 1.07 - Feb 6, 2007
  idea from Gary Lail - don't log inserts, only updates
  added pRollbackAudit procedure
  changed all stored procedure names to pName


CREATE PROC usp AS SELECT OBJECT_NAME( @@PROCID )

-----------------------------------
version 1.08 - June 25, 2008
  case sensitive cleanup
  defaults named propoerly
  defaults and columns dropped in AutoAuditDrop proc

-----------------------------------
version 1.09 - Oct 15, 2008
  fixed @tablename bug in AutoAuditDrop
  changed audit time from GetDate() to inserted.created and inserted.modified to keep these times in synch
  changed from 'data type in()' to 'data type not in (xml, varbinary, image, text)'  
  added support for hierarchyID tracking (from Cast to Convert)
  added check: Table must have PK
  added check: PK must not be HierarchyID
  added RowVersion to dbo.Audit, and insert/update/delete procs
  added RowHistory Table Valued Function
  added SchemaAudit table and database trigger
  SchemaAuditDDLTrigger also fires pAutoAudit for Alter_Table events for tables with AutoAudit

*/

-- v1.09 adds schema audit 

IF Object_id('SchemaAudit') IS NULL
  CREATE TABLE dbo.SchemaAudit (
    AuditDate DATETIME NOT NULL,
    UserName SysName NOT NULL,
    [Event] sysname NOT NULL,
    [Schema] sysname NOT NULL,
    [Object] VARCHAR(50) NOT NULL,
    [TSQL] VARCHAR(max) NOT NULL,
    [XMLEventData] XML NOT NULL
    );
go 

If Exists(select * from sys.triggers where name = 'SchemaAuditDDLTrigger')
   DROP TRIGGER SchemaAuditDDLTrigger ON Database
   
go -----------------------------------------------
 
   
CREATE TRIGGER [SchemaAuditDDLTrigger]
ON DATABASE
FOR DDL_DATABASE_LEVEL_EVENTS
AS 
BEGIN
  -- Added by AutoAudit 
  -- www.SQLServerBible.com 
  -- Paul Nielsen 
  SET NoCount ON 
  DECLARE 
    @EventData XML,
    @Schema SYSNAME,
    @Object SYSNAME,
    @EventType SYSNAME,
    @SQL VARCHAR(max)
    
  SET @EventData = EventData()
  
  SET @Schema = @EventData.value('data(/EVENT_INSTANCE/SchemaName)[1]', 'VARCHAR(50)')
  SET @Object = @EventData.value('data(/EVENT_INSTANCE/ObjectName)[1]', 'VARCHAR(50)')
  SET @EventType = @EventData.value('data(/EVENT_INSTANCE/EventType)[1]', 'VARCHAR(50)')
  
  
  INSERT SchemaAudit (AuditDate, UserName, [Event], [Schema], Object, TSQL, [XMLEventData])
  SELECT 
    GetDate(),
    @EventData.value('data(/EVENT_INSTANCE/UserName)[1]', 'SYSNAME'),
    @EventType, @Schema, @Object,
    @EventData.value('data(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'VARCHAR(max)'),
    @EventData
    
  IF  @EventType = 'ALTER_TABLE'
     AND Exists(Select * from sys.objects where name = @Object + '_Audit_Insert')
    BEGIN 
      SET @SQL = ' EXEC pAutoAudit ''' + @Schema+ ''', ''' + @Object + ''''
      EXEC (@SQL)
    END 
   
END   

go -----------------------------------------------


IF Object_id('pAutoAudit') IS NOT NULL
  DROP PROC pAutoAudit
IF Object_id('pAutoAuditAll') IS NOT NULL
  DROP PROC pAutoAuditAll
IF Object_id('pAutoAuditDrop') IS NOT NULL
  DROP PROC pAutoAuditDrop
IF Object_id('pAutoAuditDropAll') IS NOT NULL
  DROP PROC pAutoAuditDropAll

go
IF Object_id('Audit') IS NULL
	CREATE TABLE dbo.Audit (
	  AuditID BIGINT NOT NULL IDENTITY PRIMARY KEY CLUSTERED,
	  AuditDate DATETIME NOT NULL,
	  HostName sysname NOT NULL,
	  SysUser VARCHAR(50) NOT NULL,
	  Application VARCHAR(50) NOT NULL,
	  TableName sysname NOT NULL,
	  Operation CHAR(1) NOT NULL, -- i,u,d
	  PrimaryKey VARCHAR(20) NOT NULL, -- edit to suite
	  RowDescription VARCHAR(50) NULL,-- Optional
	  SecondaryRow VARCHAR(50) NULL, -- Optional 
	  ColumnName sysname NULL, -- required for i,u, and now D (ver 1.07), should add check constraint
	  OldValue VARCHAR(50) NULL, -- edit to suite (Nvarchar() ?, varchar(MAX) ? ) 
	  NewValue VARCHAR(50) NULL, -- edit to suite (Nvarchar() ?, varchar(MAX) ? )
	  [RowVersion] INT NULL
	  )  -- optimzed for inserts, no non-clustered indexes

go --------------------------------------------------------------------


-- v 1.09 add RowVersion column to dbo.Audit 
IF not exists( select *
			  from sys.tables t
				join sys.schemas s
				  on s.schema_id = t.schema_id
				join sys.columns c
				  on t.object_id = c.object_id
			  where  t.name = 'Audit' AND s.name = 'dbo' and c.name = 'RowVersion')
 ALTER TABLE dbo.Audit ADD RowVersion INT NULL


go --------------------------------------------------------------------


CREATE PROC pAutoAudit (
   @SchemaName VARCHAR(50),
   @TableName VARCHAR(50)
) 
AS 
SET NoCount ON

-- script to create autoAudit triggers
DECLARE 
   @SQL NVARCHAR(max),
   @ColumnName  sysname,
   @PKColumnName sysname, 
   @PKDataType sysname

-- drop existing insert trigger
SET @SQL = 'If EXISTS (Select * from sys.objects where name = '
       + '''' + @TableName + '_Audit_Insert' + ''' )'
       + ' DROP TRIGGER ' + @SchemaName + '.' + @TableName + '_Audit_Insert'
EXEC (@SQL)

-- drop existing update trigger
SET @SQL = 'If EXISTS (Select * from sys.objects where name = '
       + '''' + @TableName + '_Audit_Update' + ''' )'
       + ' DROP TRIGGER ' + @SchemaName + '.' + @TableName + '_Audit_Update'
EXEC (@SQL)

-- drop existing modified trigger
SET @SQL = 'If EXISTS (Select * from sys.objects where name = '
       + '''' + @TableName + '_Modified' + ''' )'
       + ' DROP TRIGGER ' + @SchemaName + '.' + @TableName + '_Modified'
EXEC (@SQL)

-- drop existing delete trigger
SET @SQL = 'If EXISTS (Select * from sys.objects where name = '
       + '''' + @TableName + '_Audit_Delete' + ''' )'
       + ' DROP TRIGGER ' + @SchemaName + '.' + @TableName + '_Audit_Delete'
EXEC (@SQL)

-- drop existing delete view
SET @SQL = 'If EXISTS (Select * from sys.objects where name = '
       + '''v' + @TableName + '_Deleted' + ''' )'
       + ' DROP VIEW ' + @SchemaName + '.v' + @TableName + '_Deleted'
EXEC (@SQL)

-- drop existing row history TV UDF
SET @SQL = 'If EXISTS (Select * from sys.objects where name = '
       + '''' + @TableName + '_RowHistory' + ''' )'
       + ' DROP FUNCTION ' + @SchemaName + '.' + @TableName + '_RowHistory'
EXEC (@SQL)

-- add created column 
IF not exists (select *
			  from sys.tables t
				join sys.schemas s
				  on s.schema_id = t.schema_id
				join sys.columns c
				  on t.object_id = c.object_id
			  where  t.name = @TableName AND s.name = @SchemaName and c.name = 'Created')
  BEGIN
    SET @SQL = 'ALTER TABLE ' + @SchemaName + '.' + @TableName + ' ADD Created DateTime NOT NULL constraint ' + @TableName + '_Created_df Default GetDate()'
    EXEC (@SQL)
  END


-- add modified column 
IF not exists( select *
			  from sys.tables t
				join sys.schemas s
				  on s.schema_id = t.schema_id
				join sys.columns c
				  on t.object_id = c.object_id
			  where  t.name = @TableName AND s.name = @SchemaName and c.name = 'Modified')
  BEGIN   
    SET @SQL = 'ALTER TABLE ' + @SchemaName + '.' + @TableName + ' ADD Modified DateTime NOT NULL constraint ' + @TableName + '_Modified_df Default GetDate()'
    EXEC (@SQL)
  END

-- add RowVersion column 
IF not exists( select *
			  from sys.tables t
				join sys.schemas s
				  on s.schema_id = t.schema_id
				join sys.columns c
				  on t.object_id = c.object_id
			  where  t.name = @TableName AND s.name = @SchemaName and c.name = 'RowVersion')
  BEGIN   
    SET @SQL = 'ALTER TABLE ' + @SchemaName + '.' + @TableName + ' ADD RowVersion INT NOT NULL constraint ' + @TableName + '_RowVersion_df Default 1'
    EXEC (@SQL)
  END


-- get PK Column (1)  
select @PKColumnName = c.name, @PKDataType = ty.name
  from sys.tables t
    join sys.schemas s
      on s.schema_id = t.schema_id
    join sys.indexes i
      on t.object_id = i.object_id
    join sys.index_columns ic
  	  on i.object_id = ic.object_id
      and i.index_id = ic.index_id
    join sys.columns c
      on ic.object_id = c.object_id
      and ic.column_id = c.column_id
	join sys.types as ty
	  on ty.user_type_id = c.user_type_id
  where is_primary_key = 1 AND t.name = @TableName AND s.name = @SchemaName AND ic.index_column_id = 1
    

-- 1.09 Table PK Check  
  IF @PKColumnName IS NULL 
  BEGIN 
    PRINT '*** ' + @SchemaName + '.' + @TableName + ' invalid Table - no Primary Key. No triggers created.'
    RETURN
  END  
  
  
-- 1.09 Table PK HierarchyID Check  
  IF @PKDataType = 'HierarchyID'
  BEGIN 
    PRINT '*** ' + @SchemaName + '.' + @TableName + ' HierarchyID PK. No triggers created.'
    RETURN
  END  
  
   
     
-- build modified trigger 
SET @SQL = 'CREATE TRIGGER ' + @SchemaName + '.' + @TableName + '_Modified' + ' ON '+ @SchemaName + '.' + @TableName + Char(13) + Char(10)
       + ' AFTER Update' + Char(13) + Char(10) + ' NOT FOR REPLICATION AS' + Char(13) + Char(10)
       + ' SET NoCount On ' + Char(13) + Char(10)
       + ' -- generated by AutoAudit on ' + Convert(VARCHAR(30), GetDate(),100)  + Char(13) + Char(10)
       + ' -- created by Paul Nielsen ' + Char(13) + Char(10)
       + ' -- www.SQLServerBible.com ' + Char(13) + Char(10) + Char(13) + Char(10)

       + ' Begin Try ' + Char(13) + Char(10)
       + ' If Trigger_NestLevel(object_ID(N''[' + @SchemaName + '].[' + @TableName + '_Modified]'')) > 1 Return;' + Char(13) + Char(10)

       + ' If (Update(Created) or Update(Modified)) AND Trigger_NestLevel() = 1' + Char(13) + Char(10)
       + ' Begin; Raiserror(''Update failed.'', 16, 1); Rollback;  Return; End;' + Char(13) + Char(10)

       + ' -- Update the Modified date' + Char(13) + Char(10)
       + ' UPDATE [' + @SchemaName + '].[' + @TableName + ']'+ Char(13) + Char(10)
       + ' SET Modified = getdate(), ' + Char(13) + Char(10)
       + '        [RowVersion] = [' + @TableName + '].[RowVersion] + 1 ' + Char(13) + Char(10)
       + '   FROM [' + @SchemaName + '].[' + @TableName + ']' + Char(13) + Char(10)
       + '     JOIN Inserted'  + Char(13) + Char(10)
       + '       ON [' + @TableName + '].[' + @PKColumnName + '] = Inserted.[' + @PKColumnName + ']'
       + ' End Try ' + Char(13) + Char(10)
       + ' Begin Catch ' + Char(13) + Char(10)
       + '   Raiserror(''error in [' + + @SchemaName + '].[' + @TableName +'_modified] trigger'', 16, 1 ) with log' + Char(13) + Char(10)
       + ' End Catch ' 

    EXEC (@SQL)

--------------------------------------------------------------------------------------------
-- build insert trigger 
SET @SQL = 'CREATE TRIGGER ' + @SchemaName + '.' + @TableName + '_Audit_Insert' + ' ON '+ @SchemaName + '.' + @TableName + Char(13) + Char(10)
       + ' AFTER Insert' + Char(13) + Char(10) + ' NOT FOR REPLICATION AS' + Char(13) + Char(10)
       + ' SET NoCount On ' + Char(13) + Char(10)
       + ' -- generated by AutoAudit on ' + Convert(VARCHAR(30), GetDate(),100)  + Char(13) + Char(10)
       + ' -- created by Paul Nielsen ' + Char(13) + Char(10)
       + ' -- www.SQLServerBible.com ' + Char(13) + Char(10) + Char(13) + Char(10)
       + 'DECLARE @AuditTime DATETIME' + Char(13) + Char(10)
       + 'SET @AuditTime = GetDate()' + Char(13) + Char(10) + Char(13) + Char(10)
       + ' Begin Try ' + Char(13) + Char(10)

-- for PK column
	select @SQL = @SQL + 
		     '   INSERT dbo.Audit (AuditDate, SysUser, Application, HostName, TableName, Operation, PrimaryKey, RowDescription, SecondaryRow, ColumnName, NewValue, RowVersion)' + Char(13) + Char(10)
          + '   SELECT  Inserted.Created, suser_sname(), APP_NAME(), Host_Name(), ' 
          + '''' + @SchemaName + '.' + @TableName + ''', ''i'','   
          + ' Inserted.[' + @PKColumnName + '],' + Char(13) + Char(10) 
          + '        NULL,     -- Row Description (e.g. Order Number)' + Char(13) + Char(10)   
          + '        NULL,     -- Secondary Row Value (e.g. Order Number for an Order Detail Line)' + Char(13) + Char(10)
          + '        ''[' + c.name + ']'','   
          + ' Cast(Inserted.[' + c.name + '] as VARCHAR(50)), 1' + Char(13) + Char(10)
          + '          FROM Inserted' + Char(13) + Char(10)
          + '          WHERE Inserted.['+ c.name + '] is not null' + Char(13) + Char(10)+ Char(13) + Char(10)
	  from sys.tables as t
		join sys.columns as c
		  on t.object_id = c.object_id
		join sys.schemas as s
		  on s.schema_id = t.schema_id
		join sys.types as ty
		  on ty.user_type_id = c.user_type_id
		-- v 1.09 changed to user type to accomodate SQL 2008 CLR data types
		--join sys.types st
		--  on ty.system_type_id = st.user_type_id
      where t.name = @TableName AND s.name = @SchemaName 
         AND c.name = @PKColumnName
         AND c.is_computed = 0
         -- version 1.09 modified list of data types
   	     -- v 1.09 changed to ty.name to accomodate SQL 2008 CLR data types
         AND ty.name NOT IN ('xml', 'varbinary', 'image', 'text', 'geography')
	  order by c.column_id

	select @SQL = @SQL + 
       + ' End Try ' + Char(13) + Char(10)
       + ' Begin Catch ' + Char(13) + Char(10)
       + '   Raiserror(''error in [' + + @SchemaName + '].[' + @TableName +'_audit_insert] trigger'', 16, 1 ) with log' + Char(13) + Char(10)
       + ' End Catch ' 
EXEC (@SQL)

--------------------------------------------------------------------------------------------
-- build update trigger 

SET @SQL = 'CREATE TRIGGER ' + @SchemaName + '.' + @TableName + '_Audit_Update' + ' ON '+ @SchemaName + '.' + @TableName + Char(13) + Char(10)
       + ' AFTER Update' + Char(13) + Char(10) + ' NOT FOR REPLICATION AS' + Char(13) + Char(10)
       + ' SET NoCount On ' + Char(13) + Char(10)
       + ' -- generated by AutoAudit on ' + Convert(VARCHAR(30), GetDate(),100)  + Char(13) + Char(10)
       + ' -- created by Paul Nielsen ' + Char(13) + Char(10)
       + ' -- www.SQLServerBible.com ' + Char(13) + Char(10) + Char(13) + Char(10)
       + 'DECLARE @AuditTime DATETIME' + Char(13) + Char(10)
       + 'SET @AuditTime = GetDate()' + Char(13) + Char(10) + Char(13) + Char(10)
       + ' Begin Try ' + Char(13) + Char(10)
-- for each column
	select @SQL = @SQL + 
	   +  ' IF UPDATE([' + c.name + '])' + Char(13) + Char(10)
       + '   INSERT dbo.Audit (AuditDate, SysUser, Application, HostName, TableName, Operation, PrimaryKey, RowDescription, SecondaryRow, ColumnName, OldValue, NewValue, RowVersion)' + Char(13) + Char(10)
       + '   SELECT  @AuditTime, suser_sname(), APP_NAME(), Host_Name(), ' 
       + '''' + @SchemaName + '.' + @TableName + ''', ''u'','  
       
       -- 1.09 handle HierarchyID PK  
       + ' Convert(VARCHAR(50), Inserted.[' + @PKColumnName + ']),' + Char(13) + Char(10) 
       ----------------
       + '        NULL,     -- Row Description (e.g. Order Number)' + Char(13) + Char(10)   
       + '        NULL,     -- Secondary Row Value (e.g. Order Number for an Order Detail Line)' + Char(13) + Char(10)
       + '        ''[' + c.name+ ']'',' 
         
  -- 1.09 changed from cast to convert to handle HierarchyID conversion 
  --     + ' Cast(Deleted.[' + c.name + '] as VARCHAR(50)), ' 
       + ' Convert(VARCHAR(50), Deleted.[' + c.name + ']), ' 
       
  --     + ' Cast(Inserted.[' + c.name + '] as VARCHAR(50))' + Char(13) + Char(10)
       + ' Convert(VARCHAR(50), Inserted.[' + c.name + ']),' + Char(13) + Char(10)
       
       + ' DELETED.Rowversion + 1' + Char(13) + Char(10)
       + '          FROM Inserted' + Char(13) + Char(10)
       + '             JOIN Deleted' + Char(13) + Char(10)
       + '               ON Inserted.[' + @PKColumnName + '] = Deleted.[' + @PKColumnName + ']' + Char(13) + Char(10)
       + '               AND isnull(Inserted.[' + c.name + '],'''') <> isnull(Deleted.[' + c.name + '],'''')' + Char(13) + Char(10)+ Char(13) + Char(10)
	  from sys.tables as t
		join sys.columns as c
		  on t.object_id = c.object_id
		join sys.schemas as s
		  on s.schema_id = t.schema_id
		join sys.types as ty
		  on ty.user_type_id = c.user_type_id
	    -- v 1.09 changed to user type to accomodate SQL 2008 CLR data types
		-- join sys.types st
		--   on ty.system_type_id = st.user_type_id
      where t.name = @TableName AND s.name = @SchemaName 
         AND c.name NOT IN ('created', 'modified','RowVersion')
         AND c.is_computed = 0
         -- version 1.09 modified list of data types
   	     -- v 1.09 changed to ty.name to accomodate SQL 2008 CLR data types
         AND ty.name NOT IN ('xml', 'varbinary', 'image', 'text', 'geography')
	  order by c.column_id

-- uniqueidentifier

	select @SQL = @SQL + 
       + ' End Try ' + Char(13) + Char(10)
       + ' Begin Catch ' + Char(13) + Char(10)
       + '   Raiserror(''error in [' + + @SchemaName + '].[' + @TableName +'_audit_update] trigger'', 16, 1 ) with log' + Char(13) + Char(10)
       + ' End Catch ' 


EXEC (@SQL)


--------------------------
-- build delete trigger 
SET @SQL = 'CREATE TRIGGER ' + @SchemaName + '.' + @TableName + '_Audit_Delete' + ' ON '+ @SchemaName + '.' + @TableName + Char(13) + Char(10)
       + ' AFTER Delete' + Char(13) + Char(10) + ' NOT FOR REPLICATION AS' + Char(13) + Char(10)
       + ' SET NoCount On ' + Char(13) + Char(10)
       + ' -- generated by AutoAudit on ' + Convert(VARCHAR(30), GetDate(),100)  + Char(13) + Char(10)
       + ' -- created by Paul Nielsen ' + Char(13) + Char(10)
       + ' -- www.SQLServerBible.com ' + Char(13) + Char(10) + Char(13) + Char(10)
       + 'DECLARE @AuditTime DATETIME' + Char(13) + Char(10)
       + 'SET @AuditTime = GetDate()' + Char(13) + Char(10) + Char(13) + Char(10)
       + ' Begin Try ' + Char(13) + Char(10)

-- for each column
	select @SQL = @SQL + 
		     '   INSERT dbo.Audit (AuditDate, SysUser, Application, HostName, TableName, Operation, PrimaryKey, RowDescription, SecondaryRow, ColumnName, OldValue, Rowversion)' + Char(13) + Char(10)
          + '   SELECT  @AuditTime, suser_sname(), APP_NAME(), Host_Name(),' 
          + '''' + @SchemaName + '.' + @TableName + ''', ''d'','   
          + ' deleted.[' + @PKColumnName + '],' + Char(13) + Char(10) 
          + '        NULL,     -- Row Description (e.g. Order Number)' + Char(13) + Char(10)   
          + '        NULL,     -- Secondary Row Value (e.g. Oder Number for an Order Detail Line)' + Char(13) + Char(10)
          + '        ''[' + c.name + ']'','   

  --     + ' Cast(Deleted.[' + c.name + '] as VARCHAR(50)), ' 
       + ' Convert(VARCHAR(50), Deleted.[' + c.name + ']), ' 
          + ' deleted.Rowversion '
          + '          FROM deleted' + Char(13) + Char(10)
          + '          WHERE deleted.['+ c.name + '] is not null' + Char(13) + Char(10)+ Char(13) + Char(10)
	  from sys.tables as t
		join sys.columns as c
		  on t.object_id = c.object_id
		join sys.schemas as s
		  on s.schema_id = t.schema_id
		join sys.types as ty
		  on ty.user_type_id = c.user_type_id
        -- v 1.09 changed to ty.name to accomodate SQL 2008 CLR data types
		--join sys.types st
		--  on ty.system_type_id = st.user_type_id
      where t.name = @TableName AND s.name = @SchemaName 
         -- AND c.name NOT IN ('created', 'modified','RowVersion')
         AND c.is_computed = 0
         -- version 1.09 modified list of data types
         -- v 1.09 changed to ty.name to accomodate SQL 2008 CLR data types
         AND ty.name NOT IN ('xml', 'varbinary', 'image', 'text', 'geography')
	  order by c.column_id

	select @SQL = @SQL + 
       + ' End Try ' + Char(13) + Char(10)
       + ' Begin Catch ' + Char(13) + Char(10)
       + '   Raiserror(''error in [' + + @SchemaName + '].[' + @TableName +'_audit_delete trigger'', 16, 1 ) with log' + Char(13) + Char(10)
       + ' End Catch ' 

EXEC (@SQL)


--------------------------------------------------------------------------------------------
-- build _Deleted view

/* Sample: 
CREATE VIEW Production.vCulture_Deleted
as
	SELECT 
		Max(Case ColumnName WHEN '[CultureID]' THEN OldValue ELSE '' END) AS [CultureID],
		Max(Case ColumnName WHEN '[Name]' THEN OldValue ELSE '' END) AS [Name],
		Max(Case ColumnName WHEN '[Created]' THEN OldValue ELSE '' END) AS [Created],
		Max(Case ColumnName WHEN '[Modified]' THEN OldValue ELSE '' END) AS Modified,
		Max(Case ColumnName WHEN '[Rowversion]' THEN OldValue ELSE '' END) AS [Rowversion],
        MAX(AuditDate) AS 'Deleted'
	FROM Audit 
	Where TableName = 'Production.Culture' AND Operation = 'd'
	GROUP BY PrimaryKey 
*/

SET @SQL = 'CREATE VIEW ' + @SchemaName + '.v' + @TableName + '_Deleted' + Char(13) + Char(10)
       + 'AS ' + Char(13) + Char(10) 
       + ' -- generated by AutoAudit on ' + Convert(VARCHAR(30), GetDate(),100)  + Char(13) + Char(10)
       + ' -- created by Paul Nielsen ' + Char(13) + Char(10)
       + ' -- www.SQLServerBible.com ' + Char(13) + Char(10) + Char(13) + Char(10)
       + 'SELECT ' + Char(13) + Char(10)

-- for each column
SELECT @SQL = @SQL +
		  '     Max(Case ColumnName WHEN ''[' + c.name + ']'' THEN OldValue ELSE '''' END) AS [' + c.name +'],'  + Char(13) + Char(10)
	  from sys.tables as t
		join sys.columns as c
		  on t.object_id = c.object_id
		join sys.schemas as s
		  on s.schema_id = t.schema_id
		join sys.types as ty
		  on ty.user_type_id = c.user_type_id
		--join sys.types st
		--  on ty.system_type_id = st.user_type_id
      where t.name = @TableName AND s.name = @SchemaName 
         AND c.is_computed = 0
         -- version 1.09 modified list of data types
         -- v 1.09 changed to ty.name to accomodate SQL 2008 CLR data types
         AND ty.name NOT IN ('xml', 'varbinary', 'image', 'text', 'geography')
	  order by c.column_id

SET @SQL = @SQL
        + '      MAX(AuditDate) AS ''Deleted'''  + Char(13) + Char(10)
	    + '  FROM Audit'   + Char(13) + Char(10)
	    + '  Where TableName = ''' +@SchemaName + '.' + @TableName + ''' AND Operation = ''d'''  + Char(13) + Char(10)
	    + '  GROUP BY PrimaryKey' 

EXEC (@SQL)

--------------------------------------------------------------------------------------------
-- build _RowHistory Table-Valued UDF

/* sample:
CREATE FUNCTION HumanResources.Department_RowHistory (@PK INT) 
RETURNS TABLE
RETURN
(
-- Initial Row Values
Select Created as AuditDate, 'i' as Operation, 
    -- Name 
    (SELECT Coalesce( 
        (Select top 1 OldValue 
          FROM dbo.Audit 
          Where TableName = 'HumanResources.Department' 
            and PrimaryKey = @PK
            and Operation = 'u' 
            and ColumnName = '[Name]'
          order by AuditID),
      Name)
    FROM HumanResources.Department
      WHERE DepartmentID = @PK
  ) as Name,
  
    -- GroupName 
   ( SELECT Coalesce( 
        (Select top 1 OldValue 
          FROM dbo.Audit 
          Where TableName = 'HumanResources.Department' 
            and PrimaryKey = @PK 
            and Operation = 'u' 
            and ColumnName = '[GroupName]'
          order by AuditID),
      GroupName)
    FROM HumanResources.Department
      WHERE DepartmentID = @PK
  ) as GroupName
  FROM HumanResources.Department
  where DepartmentID = @PK

UNION ALL

SELECT AuditDate, 'u'
     ,Max(Case ColumnName WHEN '[Name]' THEN NewValue ELSE '' END) AS [Name]
     ,Max(Case ColumnName WHEN '[GroupName]' THEN NewValue ELSE '' END) AS [GroupName]
  FROM Audit
  Where TableName = 'HumanResources.Department' 
    AND PrimaryKey = @PK
    and Operation = 'u' 
  GROUP BY PrimaryKey, AuditDate
);
*/

SET @SQL = 'CREATE FUNCTION ' + @SchemaName + '.' + @TableName + '_RowHistory (@PK INT)' + Char(13) + Char(10)
       + 'RETURNS TABLE ' + Char(13) + Char(10) 
       + ' -- generated by AutoAudit on ' + Convert(VARCHAR(30), GetDate(),100)  + Char(13) + Char(10)
       + ' -- created by Paul Nielsen ' + Char(13) + Char(10)
       + ' -- www.SQLServerBible.com ' + Char(13) + Char(10) + Char(13) + Char(10)
       + 'RETURN ' + Char(13) + Char(10)
       + '( ' + Char(13) + Char(10)
       + '-- Initial Row Values ' + Char(13) + Char(10)
       + 'Select Created as AuditDate, ''i'' as Operation, 1 as RowVersion' 

-- for each column
SELECT @SQL = @SQL 
      + ', ' + Char(13) + Char(10)
      + '-- ' + c.name + Char(13) + Char(10)
      + '(SELECT Coalesce(' + Char(13) + Char(10)
      + '    (Select top 1 OldValue' + Char(13) + Char(10)
      + '       FROM dbo.Audit' + Char(13) + Char(10)
      + '       WHERE TableName = ''' + @SchemaName + '.' + @TableName + '''' + Char(13) + Char(10)
      + '         and PrimaryKey = @PK' + Char(13) + Char(10)
      + '         and Operation = ''u'''  + Char(13) + Char(10)
      + '         and ColumnName = ''[' + c.name + ']''' + Char(13) + Char(10)
      + '       ORDER BY AuditID),' + Char(13) + Char(10)
      + '     [' + c.name + '])' + Char(13) + Char(10)
      + '  FROM ' +  @SchemaName + '.' + @TableName  + Char(13) + Char(10)
      + '  WHERE [' + @PKColumnName + '] = @PK '  + Char(13) + Char(10)
      + ' ) as ' + c.name  + Char(13) + Char(10)
	  from sys.tables as t
		join sys.columns as c
		  on t.object_id = c.object_id
		join sys.schemas as s
		  on s.schema_id = t.schema_id
		join sys.types as ty
		  on ty.user_type_id = c.user_type_id
		--join sys.types st
		--  on ty.system_type_id = st.user_type_id
      where t.name = @TableName AND s.name = @SchemaName 
         AND c.is_computed = 0
         -- version 1.09 modified list of data types
         -- v 1.09 changed to ty.name to accomodate SQL 2008 CLR data types
         AND ty.name NOT IN ('xml', 'varbinary', 'image', 'text', 'geography')
         AND c.name NOT IN ('created', 'modified','RowVersion')
	  order by c.column_id

SELECT @SQL = @SQL 
      + ' FROM HumanResources.Department' + Char(13) + Char(10)
      + '   WHERE DepartmentID = @PK )' + Char(13) + Char(10) + Char(13) + Char(10)
      
      + 'UNION ALL' + Char(13) + Char(10) + Char(13) + Char(10)
      
 -- Updated values from Audit Trail     
      + 'SELECT AuditDate, ''u'', RowVersion' + Char(13) + Char(10)

-- for each column
SELECT @SQL = @SQL 
      + '     ,Max(Case ColumnName WHEN ''[' + c.name + ']'' THEN NewValue ELSE '''' END) AS [' + c.name + ']' + Char(13) + Char(10)
	  from sys.tables as t
		join sys.columns as c
		  on t.object_id = c.object_id
		join sys.schemas as s
		  on s.schema_id = t.schema_id
		join sys.types as ty
		  on ty.user_type_id = c.user_type_id
		--join sys.types st
		--  on ty.system_type_id = st.user_type_id
      where t.name = @TableName AND s.name = @SchemaName 
         AND c.is_computed = 0
         -- version 1.09 modified list of data types
         -- v 1.09 changed to ty.name to accomodate SQL 2008 CLR data types
         AND ty.name NOT IN ('xml', 'varbinary', 'image', 'text', 'geography')
         AND c.name NOT IN ('created', 'modified','RowVersion')
	  order by c.column_id

SELECT @SQL = @SQL 
      + '  FROM dbo.Audit'  + Char(13) + Char(10)
      + '    Where TableName = '''  +@SchemaName + '.' + @TableName + ''''   + Char(13) + Char(10)
      + '      AND PrimaryKey = @PK' + Char(13) + Char(10)
      + '      and Operation = ''u'''  + Char(13) + Char(10)
      + '    GROUP BY PrimaryKey, AuditDate, RowVersion'  + Char(13) + Char(10)
     -- + ' ORDER BY RowVersion'
EXEC (@SQL)


RETURN -- END OF SPROC

go --------------------------------------------------------------------
CREATE PROC pAutoAuditDrop (
   @SchemaName VARCHAR(50),
   @TableName VARCHAR(50)
) 
AS 
SET NoCount ON

DECLARE 
   @SQL NVARCHAR(max)

-- drop default constraints

-- version 1.09 - fixed bug, missing @table _
If Exists (select * from sys.objects where name = @TableName + '_Created_df')
  BEGIN 
    SET @SQL = 'ALTER TABLE ' + @SchemaName + '.' + @TableName + ' drop constraint ' + @TableName + '_Created_df'
    EXEC (@SQL)
  END

-- version 1.09 - fixed bug, missing @table _
If Exists (select * from sys.objects where name = @TableName + '_Modified_df')
  BEGIN 
    SET @SQL = 'ALTER TABLE ' + @SchemaName + '.' + @TableName + ' drop constraint ' + @TableName + '_Modified_df'
    EXEC (@SQL)
  END

-- version 1.09 - fixed bug, missing @table _
If Exists (select * from sys.objects where name = @TableName + '_RowVersion_df')
  BEGIN 
    SET @SQL = 'ALTER TABLE ' + @SchemaName + '.' + @TableName + ' drop constraint ' + @TableName + '_RowVersion_df'
    EXEC (@SQL)
  END

-- drop created column 
IF exists (select *
			  from sys.tables t
				join sys.schemas s
				  on s.schema_id = t.schema_id
				join sys.columns c
				  on t.object_id = c.object_id
			  where  t.name = @TableName AND s.name = @SchemaName and c.name = 'Created')
  BEGIN
    SET @SQL = 'ALTER TABLE ' + @SchemaName + '.' + @TableName + ' DROP COLUMN Created'
    EXEC (@SQL)
  END

-- add modified column 
IF exists( select *
			  from sys.tables t
				join sys.schemas s
				  on s.schema_id = t.schema_id
				join sys.columns c
				  on t.object_id = c.object_id
			  where  t.name = @TableName AND s.name = @SchemaName and c.name = 'Modified')
  BEGIN   
    SET @SQL = 'ALTER TABLE ' + @SchemaName + '.' + @TableName + ' DROP COLUMN Modified'
    EXEC (@SQL)
  END

-- add RowVersion column 
IF exists( select *
			  from sys.tables t
				join sys.schemas s
				  on s.schema_id = t.schema_id
				join sys.columns c
				  on t.object_id = c.object_id
			  where  t.name = @TableName AND s.name = @SchemaName and c.name = 'RowVersion')
  BEGIN   
    SET @SQL = 'ALTER TABLE ' + @SchemaName + '.' + @TableName + ' DROP COLUMN RowVersion'
    EXEC (@SQL)
  END

-- drop existing insert trigger
SET @SQL = 'If EXISTS (Select * from sys.objects where name = '
       + '''' + @TableName + '_Audit_Insert' + ''' )'
       + ' DROP TRIGGER ' + @SchemaName + '.' + @TableName + '_Audit_Insert'
EXEC (@SQL)

-- drop existing update trigger
SET @SQL = 'If EXISTS (Select * from sys.objects where name = '
       + '''' + @TableName + '_Audit_Update' + ''' )'
       + ' DROP TRIGGER ' + @SchemaName + '.' + @TableName + '_Audit_Update'
EXEC (@SQL)

-- drop existing modified trigger
SET @SQL = 'If EXISTS (Select * from sys.objects where name = '
       + '''' + @TableName + '_Modified' + ''' )'
       + ' DROP TRIGGER ' + @SchemaName + '.' + @TableName + '_Modified'
EXEC (@SQL)

-- drop existing delete trigger
SET @SQL = 'If EXISTS (Select * from sys.objects where name = '
       + '''' + @TableName + '_Audit_Delete' + ''' )'
       + ' DROP TRIGGER ' + @SchemaName + '.' + @TableName + '_Audit_Delete'
EXEC (@SQL)

go -----------------------------------------------------------------------
CREATE 
-- ALTER 
PROC pAutoAuditAll 
AS 
SET NoCount ON 
DECLARE 
   @TableName VARCHAR(50), 
   @SchemaName VARCHAR(50), 
   @SQL NVARCHAR(max)
-- for each table
-- 1
DECLARE cTables CURSOR FAST_FORWARD READ_ONLY
  FOR  SELECT s.name, t.name 
			  from sys.tables t
				join sys.schemas s
				  on t.schema_id = s.schema_id
			 where t.name <> 'audit'
--2 
OPEN cTables
--3 
FETCH cTables INTO @SchemaName, @TableName   -- prime the cursor
WHILE @@Fetch_Status = 0 
  BEGIN
		SET @SQL = 'EXEC pAutoAudit ''' + @SchemaName + ''', ''' + @TableName + ''''
		PRINT @SQL
		EXEC (@SQL)
      FETCH cTables INTO @SchemaName, @TableName   -- fetch next
  END
-- 4  
CLOSE cTables
-- 5
DEALLOCATE cTables

RETURN 

go -----------------------------------------------------------------------
CREATE PROC pAutoAuditDropAll 
AS 
SET NoCount ON 
DECLARE 
   @TableName VARCHAR(50), 
   @SchemaName VARCHAR(50), 
   @SQL NVARCHAR(max)
-- for each table
-- 1
DECLARE cTables CURSOR FAST_FORWARD READ_ONLY
  FOR  SELECT s.name, t.name 
			  from sys.tables t
				join sys.schemas s
				  on t.schema_id = s.schema_id
			 where t.name <> 'audit'
--2 
OPEN cTables
--3 
FETCH cTables INTO @SchemaName, @TableName   -- prime the cursor
WHILE @@Fetch_Status = 0 
  BEGIN
		SET @SQL = 'EXEC pAutoAuditDrop ''' + @SchemaName + ''', ''' + @TableName + ''''
		EXEC (@SQL)
      FETCH cTables INTO @SchemaName, @TableName   -- fetch next
  END
-- 4  
CLOSE cTables
-- 5
DEALLOCATE cTables






