In my Are you ready for SQL Server 2012 or are you still partying like it is 1999? post, I wrote about how you should start using SQL Server 2005 and SQL Server 2008 functionality now in order to prepare for SQL Server 2012. I still see tons of code that is written in the pre 2005 style and people still keep using those functions, procs and statements even though SQL Server 2005 and 2008 have much better functionality.

Today we are going to take a look at DDL triggers. DDL triggers have Server or Database Scope, DDL triggers can fire in response to a T-SQL event processed in the current database, or on the current server. For example a CREATE_TABLE event can fire on a database level as well as on a server level, a DROP_DATABASE can only fire on the server level. DDL triggers can be a very powerful tool for auditing purposes as well.

Let’s take a quick look at a DDL trigger for a CREATE_TABLE event, this trigger will fire every time someone issues a CREATE TABLE T-SQL DDL statement

This trigger is created to capture all CREATE_TABLE events in the tempdb database

T-SQL
1
2
3
4
5
6
7
8
9
10
USE tempdb
GO
 
CREATE TRIGGER DBLevelsafety 
ON DATABASE 
FOR CREATE_TABLE
AS 
   PRINT 'You must disable Trigger "DBLevelsafety" to drop or alter tables!' 
   ROLLBACK
;
USE tempdb
GO

CREATE TRIGGER DBLevelsafety 
ON DATABASE 
FOR CREATE_TABLE
AS 
   PRINT 'You must disable Trigger "DBLevelsafety" to drop or alter tables!' 
   ROLLBACK
;

If you try to create a table now in the tempdb database

T-SQL
1
2
3
USE tempdb
GO
CREATE TABLE bla(id int)
USE tempdb
GO
CREATE TABLE bla(id int)

You will get the following message

You must disable Trigger “DBLevelsafety” to drop or alter tables!
Msg 3609, Level 16, State 2, Line 1
The transaction ended in the trigger. The batch has been aborted.

Let’s drop the trigger

T-SQL
1
DROP TRIGGER DBLevelsafety
DROP TRIGGER DBLevelsafety

Msg 3701, Level 11, State 5, Line 1
Cannot drop the trigger ‘DBLevelsafety’, because it does not exist or you do not have permission.

In order to drop a DDL trigger, the syntax is a little different from a DML trigger, you need to add ON DATABASE or ON ALL SERVER after the name, Try it again

T-SQL
1
DROP TRIGGER DBLevelsafety ON DATABASE 
DROP TRIGGER DBLevelsafety ON DATABASE 

Now create a DDL triggers that will fire in any database.

T-SQL
1
2
3
4
5
6
7
CREATE TRIGGER ServerLevelsafety 
ON ALL SERVER 
FOR CREATE_TABLE
AS 
   PRINT 'You must disable Trigger "ServerLevelsafety" to drop or alter tables!' 
   ROLLBACK
;
CREATE TRIGGER ServerLevelsafety 
ON ALL SERVER 
FOR CREATE_TABLE
AS 
   PRINT 'You must disable Trigger "ServerLevelsafety" to drop or alter tables!' 
   ROLLBACK
;

Go into the model database and try to create a table

T-SQL
1
2
3
4
USE model
GO
 
CREATE TABLE temp(id int)
USE model
GO

CREATE TABLE temp(id int)

You must disable Trigger “ServerLevelsafety” to drop or alter tables!
Msg 3609, Level 16, State 2, Line 2
The transaction ended in the trigger. The batch has been aborted.

Now switch to tempdb and see if you can create a table there

T-SQL
1
2
3
4
USE tempdb
GO
 
CREATE TABLE temp(id int)
USE tempdb
GO

CREATE TABLE temp(id int)

You must disable Trigger “ServerLevelsafety” to drop or alter tables!
Msg 3609, Level 16, State 2, Line 2
The transaction ended in the trigger. The batch has been aborted.

Nope, as you can see you can’t create a table in any database
Drop the trigger

T-SQL
1
DROP TRIGGER ServerLevelsafety ON ALL SERVER 
DROP TRIGGER ServerLevelsafety ON ALL SERVER 

Verify that you can indeed create a table again

T-SQL
1
2
3
4
USE tempdb
GO
 
CREATE TABLE temp(id int)
USE tempdb
GO

CREATE TABLE temp(id int)

Let’s take a look at another example. What if you want to log anytime someone issues a DROP TABLE command in any database? You can easily accomplish that, and by using the EVENTDATA function you can capture the exact command that was used.

First create a new database with one table, this table will be used to store the messages from the trigger

T-SQL
1
2
3
4
5
6
7
8
CREATE DATABASE Logs
GO
 
USE Logs
GO
 
CREATE TABLE TriggerLog(DDL VARCHAR(300),DatabaseName VARCHAR(100),ExecutedBy VARCHAR(100), EventDate DATETIME)
GO
CREATE DATABASE Logs
GO

USE Logs
GO

CREATE TABLE TriggerLog(DDL VARCHAR(300),DatabaseName VARCHAR(100),ExecutedBy VARCHAR(100), EventDate DATETIME)
GO

Create the following trigger

T-SQL
1
2
3
4
5
6
7
8
9
10
CREATE TRIGGER trDropTable
ON ALL SERVER -- A server level trigger
FOR DROP_TABLE --Event we want to capture
AS
  INSERT tempdb..TriggerLog
  SELECT EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)'), -- T-SQL command
         EVENTDATA().value('(/EVENT_INSTANCE/DatabaseName)[1]','nvarchar(max)'),  -- database name
        COALESCE(SUSER_SNAME(),USER_NAME()),
        GETDATE();
GO
CREATE TRIGGER trDropTable
ON ALL SERVER -- A server level trigger
FOR DROP_TABLE --Event we want to capture
AS
  INSERT tempdb..TriggerLog
  SELECT EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)'), -- T-SQL command
		 EVENTDATA().value('(/EVENT_INSTANCE/DatabaseName)[1]','nvarchar(max)'),  -- database name
        COALESCE(SUSER_SNAME(),USER_NAME()),
        GETDATE();
GO

The EVENT_INSTANCE/TSQLCommand/CommandText part of EVENTDATA will give us the actual command that was executed, the /EVENT_INSTANCE/DatabaseName part will give use the database name.

It is time to take it for a test, run the following block of code

T-SQL
1
2
3
4
5
6
7
8
9
10
11
USE Tempdb
GO
CREATE TABLE bla(id int)
GO
DROP TABLE bla
 
USE model
GO
CREATE TABLE bla(id int)
GO
DROP TABLE bla
USE Tempdb
GO
CREATE TABLE bla(id int)
GO
DROP TABLE bla

USE model
GO
CREATE TABLE bla(id int)
GO
DROP TABLE bla

Now if you look in your TriggerLog table, you will see 2 rows

T-SQL
1
SELECT * FROM Logs..TriggerLog
SELECT * FROM Logs..TriggerLog

As you can see we have the two rows, since we grab the database name as well we can see what database it was run in in case you have the same table name in multiple databases

DDL	        DatabaseName	ExecutedBy	EventDate
--------------  -------------   --------------- ------------------------
DROP TABLE bla	tempdb          Denis-PCDenis	2011-12-12 20:47:23.607
DROP TABLE bla	model           Denis-PCDenis	2011-12-12 20:47:23.780

Also take a look at my post Use DDL Triggers To Track Table Changes to learn how you can capture all the DDL changes that were applied to a table over time….powerful stuff!

I just scratched the surface with what you can do with DDL triggers, below is a list of all the events that you can create your trigger for.

DDL Statements That Have Server or Database Scope

DDL Events DDL Events DDL Events

CREATE_APPLICATION_ROLE (Applies to the CREATE APPLICATION ROLE statement and sp_addapprole. If a new schema is created, this event also triggers a CREATE_SCHEMA event.)

ALTER_APPLICATION_ROLE (Applies to the ALTER APPLICATION ROLE statement and sp_approlepassword.)

DROP_APPLICATION_ROLE (Applies to the DROP APPLICATION ROLE statement and sp_dropapprole.)

CREATE_ASSEMBLY

ALTER_ASSEMBLY

DROP_ASSEMBLY

CREATE_ASYMMETRIC_KEY

ALTER_ASYMMETRIC_KEY

DROP_ASYMMETRIC_KEY

ALTER_AUTHORIZATION

ALTER_AUTHORIZATION_DATABASE (Applies to the ALTER AUTHORIZATION statement when ON DATABASE is specified, and sp_changedbowner.)

CREATE_BROKER_PRIORITY

ALTER_BROKER_PRIORITY

DROP_BROKER_PRIORITY

CREATE_CERTIFICATE

ALTER_CERTIFICATE

DROP_CERTIFICATE

CREATE_CONTRACT

DROP_CONTRACT

ADD_COUNTER_SIGNATURE

DROP_COUNTER_SIGNATURE

CREATE_CREDENTIAL

ALTER_CREDENTIAL

DROP_CREDENTIAL

GRANT_DATABASE

DENY_DATABASE

REVOKE_DATABASE

CREATE_DATABASE_AUDIT_SPEFICIATION

ALTER_DATABASE_AUDIT_SPEFICIATION

DENY_DATABASE_AUDIT_SPEFICIATION

CREATE_DATABASE_ENCRYPTION_KEY

ALTER_DATABASE_ENCRYPTION_KEY

DROP_DATABASE_ENCRYPTION_KEY

CREATE_DEFAULT

DROP_DEFAULT

BIND_DEFAULT (Applies to sp_bindefault.)

UNBIND_DEFAULT (Applies to sp_unbindefault.)

CREATE_EVENT_NOTIFICATION

DROP_EVENT_NOTIFICATION

CREATE_EXTENDED_PROPERTY (Applies to sp_addextendedproperty.)

ALTER_EXTENDED_PROPERTY (Applies to sp_updateextendedproperty.)

DROP_EXTENDED_PROPERTY (Applies to sp_dropextendedproperty.)

CREATE_FULLTEXT_CATALOG (Applies to the CREATE FULLTEXT CATALOG statement and sp_fulltextcatalog when create is specified.)

ALTER_FULLTEXT_CATALOG (Applies to the ALTER FULLTEXT CATALOG statement, sp_fulltextcatalog when start_incremental, start_full, Stop, or Rebuild is specified, and sp_fulltext_database when enable is specified.)

DROP_FULLTEXT_CATALOG (Applies to the DROP FULLTEXT CATALOG statement and sp_fulltextcatalog when drop is specified.)

CREATE_FULLTEXT_INDEX (Applies to the CREATE FULLTEXT INDEX statement and sp_fulltexttable when create is specified.)

ALTER_FULLTEXT_INDEX (Applies to the ALTER FULLTEXT INDEX statement, sp_fulltextcatalog when start_full, start_incremental, or stop is specified, sp_fulltext_column, and sp_fulltext_table when any action other than create or drop is specified.)

DROP_FULLTEXT_INDEX (Applies to the DROP FULLTEXT INDEX statement and sp_fulltexttable when drop is specified.)

CREATE_FULLTEXT_STOPLIST

ALTER_FULLTEXT_STOPLIST

DROP_FULLTEXT_STOPLIST

CREATE_FUNCTION

ALTER_FUNCTION

DROP_FUNCTION

CREATE_INDEX

ALTER_INDEX (Applies to the ALTER INDEX statement and sp_indexoption.)

DROP_INDEX

CREATE_MASTER_KEY

ALTER_MASTER_KEY

DROP_MASTER_KEY

CREATE_MESSAGE_TYPE

ALTER_MESSAGE_TYPE

DROP_MESSAGE_TYPE

CREATE_PARTITION_FUNCTION

ALTER_PARTITION_FUNCTION

DROP_PARTITION_FUNCTION

CREATE_PARTITION_SCHEME

ALTER_PARTITION_SCHEME

DROP_PARTITION_SCHEME

CREATE_PLAN_GUIDE (Applies to sp_create_plan_guide.)

ALTER_PLAN_GUIDE (Applies to sp_control_plan_guide when ENABLE, ENABLE ALL, DISABLE, or DISABLE ALL is specified.)

DROP_PLAN_GUIDE (Applies to sp_control_plan_guide when DROP or DROP ALL is specified.)

CREATE_PROCEDURE

ALTER_PROCEDURE (Applies to the ALTER PROCEDURE statement and sp_procoption.)

DROP_PROCEDURE

CREATE_QUEUE

ALTER_QUEUE

DROP_QUEUE

CREATE_REMOTE_SERVICE_BINDING

ALTER_REMOTE_SERVICE_BINDING

DROP_REMOTE_SERVICE_BINDING

CREATE_SPATIAL_INDEX

RENAME (Applies to sp_rename)

CREATE_ROLE (Applies to the CREATE ROLE statement, sp_addrole, and sp_addgroup.)

ALTER_ROLE

DROP_ROLE (Applies to the DROP ROLE statement, sp_droprole, and sp_dropgroup.)

ADD_ROLE_MEMBER

DROP_ROLE_MEMBER

CREATE_ROUTE

ALTER_ROUTE

DROP_ROUTE

CREATE_RULE

DROP_RULE

BIND_RULE (Applies to sp_bindrule.)

UNBIND_RULE (Applies to sp_unbindrule.)

CREATE_SCHEMA (Applies to the CREATE SCHEMA statement, sp_addrole, sp_adduser, sp_addgroup, and sp_grantdbaccess.)

ALTER_SCHEMA (Applies to the ALTER SCHEMA statement and sp_changeobjectowner.)

DROP_SCHEMA

CREATE_SERVICE

ALTER_SERVICE

DROP_SERVICE

ALTER_SERVICE_MASTER_KEY

BACKUP_SERVICE_MASTER_KEY

RESTORE_SERVICE_MASTER_KEY

ADD_SIGNATURE

DROP_SIGNATURE

CREATE_SPATIAL_INDEX

ALTER_INDEX can be used for spatial indexes.

DROP_INDEX can be used for spatial indexes.

CREATE_STATISTICS

DROP_STATISTICS

UPDATE_STATISTICS

CREATE_SYMMETRIC_KEY

ALTER_SYMMETRIC_KEY

DROP_SYMMETRIC_KEY

CREATE_SYNONYM

DROP_SYNONYM

CREATE_TABLE

ALTER_TABLE (Applies to the ALTER TABLE statement and sp_tableoption.)

DROP_TABLE

CREATE_TRIGGER

ALTER_TRIGGER (Applies to the ALTER TRIGGER statement and sp_settriggerorder.)

DROP_TRIGGER

CREATE_TYPE (Applies to the CREATE TYPE statement and sp_addtype.)

DROP_TYPE (Applies to the DROP TYPE statement and sp_droptype.)

CREATE_USER (Applies to the CREATE USER statement, sp_adduser, and sp_grantdbaccess.)

ALTER_USER (Applies to ALTER USER statement and sp_change_users_login.)

DROP_USER (Applies to the DROP USER statement, sp_dropuser, and sp_revokedbaccess.)

CREATE_VIEW

ALTER_VIEW

DROP_VIEW

CREATE_XML_INDEX

ALTER_INDEX can be used for XML indexes.

DROP_INDEX can be used for XML indexes.

CREATE_XML_SCHEMA_COLLECTION

ALTER_XML_SCHEMA_COLLECTION

DROP_XML_SCHEMA_COLLECTION

DDL Statements That Have Server Scope

DDL Events DDL Events DDL Events

ALTER_AUTHORIZATION_SERVER

ALTER_SERVER_CONFIGURATION

ALTER_INSTANCE (Applies to sp_configure and sp_addserver when a local server instance is specified.)

CREATE_CREDENTIAL

ALTER_CREDENTIAL

DROP_CREDENTIAL

CREATE_CRYPTOGRAPHIC_PROVIDER

ALTER_CRYPTOGRAPHIC_PROVIDER

DROP_CRYPTOGRAPHIC_PROVIDER

CREATE_DATABASE

ALTER_DATABASE (Applies to the ALTER DATABASE statement and sp_fulltext_database.)

DROP_DATABASE

CREATE_ENDPOINT

ALTER_ENDPOINT

DROP_ENDPOINT

CREATE_EVENT_SESSION

ALTER_EVENT_SESSION

DROP_EVENT_SESSION

CREATE_EXTENDED_PROCEDURE (Applies to sp_addextendedproc.)

DROP_EXTENDED_PROCEDURE (Applies to sp_dropextendedproc.)

CREATE_LINKED_SERVER (Applies to sp_addlinkedserver.)

ALTER_LINKED_SERVER (Applies to sp_serveroption.)

DROP_LINKED_SERVER (Applies to sp_dropserver when a linked server is specified.)

CREATE_LINKED_SERVER_LOGIN (Applies to sp_addlinkedsrvlogin.)

DROP_LINKED_SERVER_LOGIN (Applies to sp_droplinkedsrvlogin.)

CREATE_LOGIN (Applies to the CREATE LOGIN statement, sp_addlogin, sp_grantlogin, xp_grantlogin, and sp_denylogin when used on a nonexistent login that must be implicitly created.)

ALTER_LOGIN (Applies to the ALTER LOGIN statement, sp_defaultdb, sp_defaultlanguage, sp_password, and sp_change_users_login when Auto_Fix is specified.)

DROP_LOGIN (Applies to the DROP LOGIN statement, sp_droplogin, sp_revokelogin, and xp_revokelogin.)

CREATE_MESSAGE (Applies to sp_addmessage.)

ALTER_MESSAGE (Applies to sp_altermessage.)

DROP_MESSAGE (Applies to sp_dropmessage.)

CREATE_REMOTE_SERVER (Applies to sp_addserver.)

ALTER_REMOTE_SERVER (Applies to sp_setnetname.)

DROP_REMOTE_SERVER (Applies to sp_dropserver when a remote server is specified.)

GRANT_SERVER

DENY_SERVER

REVOKE_SERVER

ADD_SERVER_ROLE_MEMBER

DROP_SERVER_ROLE_MEMBER

CREATE_SERVER_AUDIT

ALTER_SERVER_AUDIT

DROP_SERVER_AUDIT

CREATE_SERVER_AUDIT_SPECIFICATION

ALTER_SERVER_AUDIT_SPECIFICATION

DROP_SERVER_AUDIT_SPECIFICATION

CREATE_RESOURCE_POOL

ALTER_RESOURCE_POOL

DROP_RESOURCE_POOL

CREATE_WORKLOAD_GROUP

CREATE_WORKLOAD_GROUP

CREATE_WORKLOAD_GROUP

Come back tomorrow for the next post in this series