See article http://www.simple-talk.com/sql/sql-server-2005/sql-server-2005-ddl-TRIGGER-workbench/ which describes
SQL Server 2005 DDL Triggers.
I have implemented these with some amendments:-
CREATE TABLE [dbo].[DDLChangeLog](
[DatabaseName] [nvarchar](50) NOT NULL CONSTRAINT [DF_ddl_log_DatabaseName] DEFAULT (DB_NAME()),
[DDLChangeLog_ID] [int] IDENTITY(1,1) NOT NULL,
[InsertionDate] [datetime] NOT NULL CONSTRAINT [DF_ddl_log_InsertionDate] DEFAULT (GETDATE()),
[CurrentUser] [nvarchar](50) NOT NULL CONSTRAINT [DF_ddl_log_CurrentUser] DEFAULT (CONVERT([nvarchar](50),USER_NAME(),(0))),
[LoginName] [nvarchar](50) NOT NULL CONSTRAINT [DF_DDLChangeLog_LoginName] DEFAULT (CONVERT([nvarchar](50),SUSER_SNAME(),(0))),
[Username] [nvarchar](50) NOT NULL CONSTRAINT [DF_DDLChangeLog_Username] DEFAULT (CONVERT([nvarchar](50),original_login(),(0))),
[EventType] [nvarchar](100) NULL,
[objectName] [nvarchar](100) NULL,
[objectType] [nvarchar](100) NULL,
[tsql] [nvarchar](MAX) NULL,
CONSTRAINT [PK_DDLChangeLog] PRIMARY KEY CLUSTERED (
[DDLChangeLog_ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
GO SET ANSI_PADDING OFF GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'The Database where the trigger was fired' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'DDLChangeLog', @level2type=N'COLUMN',@level2name=N'DatabaseName' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Reference' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'DDLChangeLog', @level2type=N'COLUMN',@level2name=N'DDLChangeLog_ID' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Time the trigger was fired' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'DDLChangeLog', @level2type=N'COLUMN',@level2name=N'InsertionDate' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'User running the change' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'DDLChangeLog', @level2type=N'COLUMN',@level2name=N'CurrentUser' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Logged in User' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'DDLChangeLog', @level2type=N'COLUMN',@level2name=N'LoginName' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Originally logged in as' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'DDLChangeLog', @level2type=N'COLUMN',@level2name=N'Username' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Event action' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'DDLChangeLog', @level2type=N'COLUMN',@level2name=N'EventType' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Object name' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'DDLChangeLog', @level2type=N'COLUMN',@level2name=N'objectName' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Object Type' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'DDLChangeLog', @level2type=N'COLUMN',@level2name=N'objectType' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Code applied' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'DDLChangeLog', @level2type=N'COLUMN',@level2name=N'tsql' GO
I have implemented on all databases on my server to do this run the results of the following
script this will add a DDL trigger to all databases on a server. Note that when the trigger
fires it will be populating the [Servername].[dbo].[DDLChangeLog] table. The table/database
name should be changed so you are recording into a central database.
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SET LOCK_TIMEOUT 10000 SET FMTONLY OFF
SET NOCOUNT ON
IF EXISTS (SELECT * FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'#temp') AND type IN (N'U')) DROP TABLE #temp GO
CREATE TABLE #Temp (ID INT IDENTITY (1,1), DatabaseName VARCHAR (150))
DECLARE @Counter INT
,@Maxcount INT
,@DatabaseName VARCHAR(150)
,@SQL VARCHAR(1000)
INSERT INTO #Temp
(DatabaseName)
SELECT [name] AS DatabaseName
FROM master.dbo.sysdatabases
WHERE [name] NOT IN ('msdb','temp')
SET @Counter = 1
SET @Maxcount = (SELECT MAX(id) FROM #Temp)
WHILE @Counter <= @maxcount
BEGIN
SET @DatabaseName = (SELECT DatabaseName
FROM #Temp
WHERE ID = @Counter)
SET @SQL = 'USE '+@DatabaseName + CHAR(13) + CHAR(10)
SET @SQL = @SQL +' GO '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +'CREATE TRIGGER trgLogDDLEvent_'+@DatabaseName+' ON DATABASE '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +'FOR DDL_DATABASE_LEVEL_EVENTS '+CHAR(13) + CHAR(10)
SET @SQL = @SQL +'AS '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +'DECLARE @data XML '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +'SET @data = EVENTDATA() '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' IF @data.value(''(/EVENT_INSTANCE/EventType)[1]'', ''nvarchar(100)'') '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' <> ''CREATE_STATISTICS'' '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' INSERT INTO [Servername].[dbo].[DDLChangeLog] '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' ( EventType, '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' ObjectName,' + CHAR(13) + CHAR(10)
SET @SQL = @SQL +' ObjectType,' + CHAR(13) + CHAR(10)
SET @SQL = @SQL +' tsql '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' ) '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' VALUES ( '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' @data.value(''(/EVENT_INSTANCE/EventType)[1]'', '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' ''nvarchar(100)''), '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' @data.value(''(/EVENT_INSTANCE/ObjectName)[1]'', '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' ''nvarchar(100)''), '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' @data.value(''(/EVENT_INSTANCE/ObjectType)[1]'', ' + CHAR(13) + CHAR(10)
SET @SQL = @SQL +' ''nvarchar(100)''), ' + CHAR(13) + CHAR(10)
SET @SQL = @SQL +' @data.value(''(/EVENT_INSTANCE/TSQLCommand)[1]'', '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' ''nvarchar(max)'') '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +' ) ; '+ CHAR(13) + CHAR(10)
SET @SQL = @SQL +'GO '+ CHAR(13) + CHAR(10)
PRINT @SQL
SET @Counter = @Counter+1
END
DROP TABLE #Temp
To help with housekeeping I have created a archiving routine, to ensure that the growth
of the DDLChangeLog stays manageable. Initially I have populated it with 10 years.
Therefore data in this archive table will be kept for 10 years. The value can be easily changed.
CREATE TABLE [dbo].[Retain](
[RetainPeriod] [char](1) NOT NULL,
[RetainValue] [int] NOT NULL,
[Notes] [varchar](50) NULL,
CONSTRAINT [PK_Retain] PRIMARY KEY CLUSTERED (
[RetainPeriod] ASC,
[RetainValue] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
GO
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Enter with Y,M or D for year, month or Day' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Retain', @level2type=N'COLUMN',@level2name=N'RetainPeriod' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Number of Years, Months or Days to retain daya for.' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Retain', @level2type=N'COLUMN',@level2name=N'RetainValue' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'Enter notes' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Retain', @level2type=N'COLUMN',@level2name=N'Notes' GO ALTER TABLE [dbo].[Retain] WITH CHECK ADD CONSTRAINT [CK_Retain] CHECK (([RetainPeriod]='D' OR [RetainPeriod]='M' OR [RetainPeriod]='Y')) GO ALTER TABLE [dbo].[Retain] CHECK CONSTRAINT [CK_Retain]
Populate the Retain TABLE
Decide how long you want to keep the information for and populate the Retain table, the following example will ensure that the data is kept for 10 years, you may want to alter this number.
VALUES acceptable Y,M OR D FOR years, months OR days.
INSERT INTO [Cumbria].[dbo].[Retain]
([RetainPeriod]
,[RetainValue]
,[Notes])
VALUES
('Y'
,10
,'Retains data for 10 years')
Schedule a job to run the following code at regular intervals to delete the file based on values in the retain table:-
DECLARE @RetainValue INT,
@RetainDate DATETIME
SET @RetainValue = (SELECT MAX(RetainValue)
FROM {database}.dbo.Retain)
SET @RetainDate = (SELECT
CASE RetainPeriod
WHEN 'Y' THEN DATEADD(YEAR,-@RetainValue, GETDATE())
WHEN 'M' THEN DATEADD(MONTH,-@RetainValue, GETDATE())
WHEN 'D' THEN DATEADD(DAY,-@RetainValue, GETDATE())
ELSE DATEADD(YEAR,100,GETDATE())
END AS RetainDate FROM {database}.dbo.Retain)
DELETE FROM {database}.[dbo].[DDLChangeLog]
WHERE InsertionDate <= @RetainDate