This was used to solve a specific problem, but some of the techniques here may be of general interest.
A DDL Trigger, operates at the database level, to capture DDL changes. There are many good articles about using DDL triggers. I’m not going to repeat the same info here; instead, here is an article that’s pretty good, about DDL Triggers: https://www.mssqltips.com/sqlservertip/2085/sql-server-ddl-triggers-to-track-all-database-changes/
So, the scenario is that we have a table where DDL changes are logged, via the DDL Trigger. The log includes the actual SQL statement that was run at that time. The changes to the database needed to be scripted out to .sql files.
The solution here, uses SQL’s ability to instantiate COM objects, and the Windows Scripting FileSystemObject COM component.
To allow SQL to use COM Objects, we have to use the sp_configure system stored procedure:
--this allows SQL server to instantiate COM objects: sp_configure 'show advanced options', 1; GO RECONFIGURE; GO sp_configure 'Ole Automation Procedures', 1; GO RECONFIGURE; GO
Next, create a stored procedure that will write to file:
--Create the sproc: SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE PROCEDURE [dbo].[WriteToTextFile] @File VARCHAR(2000), @Text VARCHAR(MAX), @AppendYN CHAR(1) = 'N' AS BEGIN DECLARE @OLE INT DECLARE @FileID INT DECLARE @OverwriteAppendMode INT IF @AppendYN = 'Y' SET @OverwriteAppendMode = 8 ELSE SET @OverwriteAppendMode = 2 EXECUTE sp_OACreate 'Scripting.FileSystemObject', @OLE OUT /* The sp_OAMethod is executing method of the given object, in this case is a FileSystemObject OpenTextFile has 4 parameters in this order: FilePath -Required. The name of the file to open Mode - Optional. How to open the file 1 = Reading - Open a file for reading. You cannot write to this file. 2 = Writing - Open a file for writing. 8 = Appending - Open a file and write to the end of the file. Create - Optional. Sets whether a new file can be created if the filename does not exist. True indicates that a new file can be created, and False indicates that a new file will not be created. False is default. Format - Optional. The format of the file 0 = TristateFalse - Open the file as ASCII. This is default. 1 = TristateTrue - Open the file as Unicode. 2 = TristateUseDefault - Open the file using the system default. */ EXECUTE sp_OAMethod @OLE, 'OpenTextFile', @FileID OUT, @File, @OverwriteAppendMode , 1, 0 EXECUTE sp_OAMethod @FileID, 'WriteLine', Null, @Text EXECUTE sp_OADestroy @FileID EXECUTE sp_OADestroy @OLE END
Here is the actual code that writes the changes to files.
DECLARE @objectname varchar(250)
DECLARE @objecttype varchar(100)
DECLARE @db VARCHAR(100)
DECLARE @eventtype VARCHAR(100)
DECLARE @hostPCname VARCHAR(100)
DECLARE @eventdate DATETIME
DECLARE @sqlcommand VARCHAR(MAX)
DECLARE @Path VARCHAR(2000)
DECLARE @Txt VARCHAR(max)
DECLARE @headertext VARCHAR(2000)
DECLARE @appendThis CHAR(1)
DECLARE @c AS CURSOR
SET @c = CURSOR FOR
SELECT databasename, eventtype, objectname, objecttype, sqlcommand, eventdate, HostPCName
FROM DDLChangeLog
ORDER BY logid
OPEN @c
FETCH NEXT FROM @c INTO @db, @eventtype, @objectname, @objecttype, @sqlcommand, @eventdate, @HostPCName
WHILE @@FETCH_STATUS = 0
BEGIN
-- procs, views and functions, just replace the entire file with new defn
-- other objects, append for ALTERs, replace for CREATEs/DROPs
SELECT @appendThis = CASE WHEN @objecttype IN ('PROCEDURE', 'VIEW', 'FUNCTION') THEN 'N'
WHEN @eventtype LIKE 'ALTER%' THEN 'Y' ELSE 'N' END
SELECT @headertext = N'
/**********************************************
Object Name: @objectname
Database : @db
Changed On : @eventdate
Changed By : @hostPCName
*/
'
SELECT @headertext = REPLACE(@headertext , '@objectname', @objectname)
SELECT @headertext = REPLACE(@headertext , '@db', @db)
SELECT @headertext = REPLACE(@headertext , '@eventdate', CONVERT(VARCHAR(20),@eventdate,120) )
SELECT @headertext = REPLACE(@headertext , '@hostPCName', @hostPCName)
SELECT @txt = @headertext + @sqlcommand
SELECT @Path = N'\\servername\sharename\SQLout\' + @objecttype + N'\[dbo].['+ @objectname + N'].sql'
EXEC [dbo].[WriteToTextFile] @Path, @Txt, @appendThis
--could use xp_cmdshell here, to check .sql file into source control, or copy it etc...
FETCH NEXT FROM @c INTO @db, @eventtype, @objectname, @objecttype, @sqlcommand, @eventdate, @HostPCName
END
CLOSE @c
DEALLOCATE @c
The DDLChangeLog table is like this:
CREATE TABLE [dbo].[DDLChangeLog]( [LogId] [int] IDENTITY(1,1) NOT NULL, [DatabaseName] [VARCHAR](256) NOT NULL, [EventType] [VARCHAR](50) NOT NULL, [ObjectName] [VARCHAR](256) NOT NULL, [ObjectType] [VARCHAR](25) NOT NULL, [SqlCommand] [VARCHAR](MAX) NOT NULL, [EventDate] [DATETIME] NOT NULL CONSTRAINT [DF_DDLChangeLog_EventDate] DEFAULT (GETDATE()), [LoginName] [vARCHAR] (256) NOT NULL, [HostPCName] [vARCHAR] (250) NULL )
The DDL Trigger is like this, simple:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [backup_objects]
ON DATABASE
FOR CREATE_PROCEDURE, ALTER_PROCEDURE, DROP_PROCEDURE,
CREATE_TABLE, ALTER_TABLE, DROP_TABLE,
CREATE_VIEW, ALTER_VIEW, DROP_VIEW,
CREATE_FUNCTION, ALTER_FUNCTION, DROP_FUNCTION
AS
SET NOCOUNT ON
DECLARE @data xml
SET @data = EVENTDATA()
INSERT INTO DDLChangeLog(databasename, eventtype,
objectname, objecttype, sqlcommand, loginname, HostPCName)
VALUES(
@data.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'varchar(256)'),
@data.value('(/EVENT_INSTANCE/EventType)[1]', 'varchar(50)'),
@data.value('(/EVENT_INSTANCE/ObjectName)[1]', 'varchar(256)'),
@data.value('(/EVENT_INSTANCE/ObjectType)[1]', 'varchar(25)'),
@data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'varchar(max)'),
@data.value('(/EVENT_INSTANCE/LoginName)[1]', 'varchar(256)'),
CAST( HOST_NAME() AS VARCHAR(250))
)
GO
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
ENABLE TRIGGER [backup_objects] ON DATABASE
GO