Problem
A previous tip, SQL Server DDL Triggers to Track All Database Changes, generated a lot of discussion involving ways to make the provided DDL trigger more useful.
In this tip I address some of those questions with solutions to extend the DDL
trigger functionality.
Solution
I wanted to post a follow-up to address a few of those questions and ideas
from my earlier tip, as several of them would not be well represented in a comment.
Scott C pointed out that the DDL trigger may fail if the user does not have INSERT permissions on the audit table.
It's quite true; the DDL trigger will execute in the context of the
caller. The caller must have adequate permissions to create/change the
object in order
to be successful (and also for the trigger to be fired in the first
place), but may not have the permissions to insert into the audit table.
Assuming you have the audit table set up this way:
CREATE TABLE dbo.DDLEvents(EventDate DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,EventType NVARCHAR(64), EventDDL NVARCHAR(MAX),aName NVARCHAR(255)EventXML XML, DatabaseName NVARCHAR(255), Sche m, ObjectName NVARCHAR(255), HostName VARCHAR(64),LoginName NVARCHAR(255) )IPAddress VARCHAR(32), ProgramName NVARCHAR(255), ;GO
One way to ensure that the DDL trigger will never fail would be to
grant read/write privilege to the public role (note that I do not grant
update or delete, so that users can't - by default - cover up their
tracks):
GRANT SELECT, INSERT ON OBJECT::dbo.DDLEvents TO [public];GO
You can, of course, get more granular than that to avoid the public
catch-all; how important that is will probably depend on how many
individual logins/users you have (and whether you want to properly map
all of those users in the audit database, assuming you are capturing DDL
events centrally for more than one database).
Johnny asked: how to get the latest version of a procedure *and* the one before it?
In my original query, I simply returned the original and latest
version of a procedure, ignoring all of the modifications in between.
Sometimes it is more useful to compare the latest change with the
version that existed immediately prior. Here is one way:
;WITH e AS(SELECTEventDate,DatabaseName,SchemaName,LoginName,ObjectName, EventDDL,( PARTITION BY Darn = ROW_NUMBER() OVER tabaseName, SchemaName, ObjectNameESC ) FROMORDER BY EventDate DAuditDB.dbo.DDLEvents ) SELECT cur.DatabaseName, cur.SchemaName,n] = prev.EventDDcur.ObjectName, cur.EventDate, cur.LoginName, [PreviousVersi oL, [CurrentVersion] = cur.EventDDL FROM e AS cur INNER JOIN e AS prevme AND cur.ObjectName = prev.ObjectNamON cur.DatabaseName = prev.DatabaseName AND cur.SchemaName = prev.SchemaN ae WHERE cur.rn = 1AND prev.rn = 2;
And if you're running SQL Server 2012, you can do this slightly easier using the new
LAG()
function:;WITH e AS(SELECTDatabaseName,SchemaName,EventDate,ObjectName, LoginName,1) OVER ( PARTITION BY DatabPreviousVersion = LAG(EventDDL ,aseName, SchemaName, ObjectName ORDER BY EventDate ),PARTITION BY DatabaseNameCurrentVersion = EventDDL, rn = ROW_NUMBER() OVER ( , SchemaName, ObjectName ORDER BY EventDate DESC ) FROMate, LoginName, [PreviouAuditDB.dbo.DDLEvents ) SELECT DatabaseName, SchemaName, ObjectName, Event DsVersion], [CurrentVersion]Version IS NOT NULL;FROM e WHERE rn = 1 AND Previou s
Jaya asked how to create a mirror table with the same structure but a slightly different name.
This would presumably be used to serve as an archive table or perhaps
an empty table used for partition switching. One way would be to
reissue the same T-SQL command with the table name replaced. I'll use
the following example as a DDL trigger *separate* from the one you use
to audit all DDL commands:
CREATE TRIGGER DDL_CopyNewTableON DATABASEABLE AS BEGIN SEFOR CREATE_ TT NOCOUNT ON;entData XML = EVENTDATA(); DECLARE @sDECLARE @Evql NVARCHAR(MAX) = @EventData.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'NVARCHAR(MAX)'),@t SYSNAME = @EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)');SELECT @sql = REPLACE(@sql, @t, N'Prefix' + @t); EXEC sp_executesql @sql; ENDGO
Some caveats:
- This assumes a very simplistic table structure, with no foreign key constraints or other external dependencies.
- The use of
REPLACE()
also makes some simplistic assumptions about the table name in use - e.g. it will not exist as part of another name in the code and, if it exists in a constraint name, that it is also ok to alter the name of the constraint. - If you encounter an error - e.g you've explicitly named a constraint
without the name of the table, or the table with the prefix appended
already exists, or the user doesn't have permission to create a table,
the trigger will roll back the
CREATE TABLE
as well. Wrapping this inTRY/CATCH
does not help. - The code will not capture indexes or constraints added after the fact - it only executes the source
CREATE TABLE
statement. - As a separate trigger, you cannot control what order the DDL triggers fire, so this second
CREATE TABLE
may or may not be audited. You may want to combine the logic if you want to do both, and as one trigger you can decide if you want to log the secondCREATE TABLE
. In my tests, both triggers fired correctly (the trigger I added second, that creates the copy of the table, fired first, and it was captured in the audit table).
A simpler approach might be to just issue a
SELECT INTO
, which will avoid some of the above issues (but not all): ALTER TRIGGER DDL_CopyNewTableON DATABASEABLE AS BEGIN SEFOR CREATE_ TT NOCOUNT ON;entData XML = EVENTDATA(); DECLARE @s SYDECLARE @EvSNAME = @EventData.value('(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(255)'),@t SYSNAME = @EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)');DECLARE @sql NVARCHAR(MAX) = N'SELECT * INTO '@t) + ' FROM ' + QUOTENAME(@s) + '.' + QUOTENAME+ QUOTENAME(@s) + '.' + QUOTENAME(N'Prefix' +(@t); PRINT @sql; EXEC sp_executesql @sql; ENDGO
Jaya also asked about maintaining this copy when the source table is
modified in the future; I'll deal with that one next time. :-)
Conclusion
The
previous tip
brought about a very healthy conversation, with lots of follow-up
questions. I've addressed a few of them here, and plan to address a few
more in a future tip.
Source Collected from MSSQLTIPS.COM
No comments :
Post a Comment