SQL SERVER – 2005 – Server and Database Level DDL Triggers Examples and Explanation

DDL triggers fire when somebody changes the shape of your database rather than the data in it. A dropped table, an altered column, a new login. They are the cheapest way to answer the question that comes up after every unexplained change, which is simply who did this.

A row of brass bell cranks with one pulled and its wire taut

DDL and DML Are Different Jobs

A normal trigger, a DML trigger, watches INSERT, UPDATE and DELETE. It cares about rows.

A DDL trigger watches CREATE, ALTER and DROP. It cares about objects. Nobody inserted anything, somebody rebuilt something, and that is the kind of change that is hardest to explain a week later.

Two Levels

A database level trigger lives in one database and sees changes to objects in it. Tables, views, procedures, users.

A server level trigger lives on the instance and sees changes that are bigger than one database. Logins, new databases, linked servers. It fires no matter which database the person was in.

Pick the smaller one that answers your question. A server level trigger that fires on everything slows every deployment down.

A Database Level Trigger

This one ran on SQL Server 2025. It reports the event rather than blocking it.

CREATE TRIGGER trg_DDLDemo ON DATABASE
FOR CREATE_TABLE, ALTER_TABLE, DROP_TABLE
AS
BEGIN
    DECLARE @d xml = EVENTDATA();
    SELECT @d.value('(/EVENT_INSTANCE/EventType)[1]',  'nvarchar(100)') AS event_type,
           @d.value('(/EVENT_INSTANCE/ObjectName)[1]', 'nvarchar(128)') AS object_name,
           @d.value('(/EVENT_INSTANCE/LoginName)[1]',  'nvarchar(128)') AS login_name;
END

Creating and then dropping a table produced exactly this:

event_type    object_name  login_name
CREATE_TABLE  TrigTest     CORP\dba

event_type    object_name  login_name
DROP_TABLE    TrigTest     CORP\dba

EVENTDATA Is the Whole Point

EVENTDATA returns XML describing what just happened. It is only populated inside the trigger. Call it anywhere else and you get nothing.

The fields worth knowing are EventType, ObjectName, SchemaName, DatabaseName, LoginName and TSQLCommand. That last one holds the statement that was run. It is usually the field people wanted all along.

A SELECT inside a trigger is fine for a demonstration. In production nobody is watching. Write it to a table instead.

CREATE TABLE dbo.DDLLog (
    id          int IDENTITY(1,1) PRIMARY KEY,
    logged_at   datetime2(0)  NOT NULL DEFAULT SYSDATETIME(),
    event_type  nvarchar(100),
    object_name nvarchar(128),
    login_name  nvarchar(128),
    command     nvarchar(max)
);

Then have the trigger insert into it, pulling TSQLCommand out of the same XML.

Blocking a Change Instead of Recording It

A DDL trigger can refuse. ROLLBACK inside it cancels the statement that fired it.

CREATE TRIGGER trg_NoDrop ON DATABASE
FOR DROP_TABLE
AS
BEGIN
    RAISERROR ('Tables are not dropped on this database without a change request.', 16, 1);
    ROLLBACK;
END

Useful where a dropped table ruins an afternoon. Be careful. It stops your own deployment scripts too.

A Server Level Trigger

CREATE TRIGGER trg_LoginAudit ON ALL SERVER
FOR CREATE_LOGIN, ALTER_LOGIN, DROP_LOGIN
AS
BEGIN
    DECLARE @d xml = EVENTDATA();
    INSERT INTO master.dbo.LoginAudit (event_type, object_name, login_name)
    SELECT @d.value('(/EVENT_INSTANCE/EventType)[1]',  'nvarchar(100)'),
           @d.value('(/EVENT_INSTANCE/ObjectName)[1]', 'nvarchar(128)'),
           @d.value('(/EVENT_INSTANCE/LoginName)[1]',  'nvarchar(128)');
END

Note ON ALL SERVER rather than ON DATABASE, and note that the table it writes to has to exist somewhere every session can reach.

Finding the Ones You Already Have

SELECT name, parent_class_desc, create_date, modify_date, is_disabled
FROM sys.triggers
WHERE parent_class IN (0, 1) AND is_ms_shipped = 0;

SELECT name, create_date, modify_date, is_disabled
FROM sys.server_triggers;

Two views, because the two levels are stored apart. A server trigger never appears in sys.triggers. People waste time looking for it there.

How to Get Back In

Read this part before you write your first one. A bad DDL trigger can lock you out of your own schema. A server level one can be worse.

DISABLE TRIGGER trg_NoDrop ON DATABASE;
DISABLE TRIGGER ALL ON DATABASE;
DISABLE TRIGGER ALL ON ALL SERVER;

If a server level trigger refuses even that, start SQL Server in single user mode with the minimal configuration flag. Server triggers do not fire then. That is deliberate, and it exists for this exact situation.

What I Would Actually Use Them For

Recording schema changes with the statement text, so who changed the column has an answer. Blocking DROP on a few tables that must never go. Noticing a new login where logins should be rare.

What I would not do is build a full audit system out of them. SQL Server Audit was made for that. It is cheaper, and it does not run your code in the middle of a deployment.

A DDL trigger is not an audit system, it is a bell on the door of the room where the shape of your database lives.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

SQL Scripts, SQL Server, SQL Stored Procedure, SQL Trigger
Previous Post
SQL SERVER – UDF – Function to Get Previous And Next Work Day – Exclude Saturday and Sunday
Next Post
XQuery FLWOR: Filter and Rebuild a Small XML Result

Related Posts

50 Comments. Leave new

  • Hello Niranjan,

    Create an INSTEAD OF trigger that raises an error, so the INSERT, UPDATE or DELETE is stopped and the caller knows why:

    CREATE TRIGGER dbo.trg_BlockChanges
    ON dbo.YourTable
    INSTEAD OF INSERT, UPDATE, DELETE
    AS
    BEGIN
    THROW 50001, ‘Changes to this table are not allowed.’, 1;
    END;

    An INSTEAD OF trigger with no statements inside also stops the change, but silently, so the application thinks it worked. If you only need to stop certain users, DENY INSERT, UPDATE, DELETE on the table is simpler than a trigger.

    Regards,
    Pinal Dave

    Reply
  • hello sir,

    can we pass parameter to trigger

    Reply
  • Dear Sir i am using below code in Sql2005 and getting error . Please help me.
    USE Test

    GO

    CREATE TRIGGER PreventDropSP

    ON Test

    FOR DROP_PROCEDURE

    AS

    PRINT ‘Dropping Procedure is not allowed.’

    ROLLBACK;

    GO

    “””Msg 170, Level 15, State 1, Procedure PreventDropSP, Line 6
    Line 6: Incorrect syntax near ‘DROP_PROCEDURE’.””””””

    Reply
    • hai ved,

      use test
      go

      create trigger preventdropsp

      on database

      for drop_procedure

      as

      print PRINT ‘Dropping Procedure is not allowed.’

      ROLLBACK;

      GO

      Regards
      kalyan

      Reply
  • Hello Ved,

    Use ON DATABASE rather than ON Test. Run this in the database you want to protect:

    USE Test;
    GO
    CREATE TRIGGER PreventDropSP
    ON DATABASE
    FOR DROP_PROCEDURE
    AS
    BEGIN
    PRINT ‘Dropping procedures is not allowed.’;
    ROLLBACK TRANSACTION;
    END;
    GO

    Please test it in a test database first and keep a way to disable the trigger if it blocks a needed change.

    Regards,
    Pinal Dave

    Reply
    • Dear Sir

      Thanks for your reply .However i am getting same error ..

      Can you please give me any one of DDL Trigger Example.

      Thanks
      Ved

      Reply
  • Hello,

    I wish to track when anyone does INSERT,UPDATE, DELETE on any of the tables in a database. Is it possible to write database level trigger for this case?

    Thanks,
    -Saroj

    Reply
  • minakshi bhardwaj
    October 11, 2010 10:03 pm

    hi sir i m creating trigger as
    create trigger check_id
    ON dbo.teacher
    AFTER INSERT,UPDATE
    AS
    BEGIN
    SET NOCOUNT ON;
    IF(t_no<100)
    THEN
    print('not allowed');
    END;
    but getting an error
    Msg 156, Level 15, State 1, Procedure check_id, Line 8
    Incorrect syntax near the keyword 'THEN'
    pls. help me as ma a beginner in sql .

    Reply
    • Hi,
      There is an syntax error in the IF. check this out.
      create trigger check_id
      ON dbo.interest_rate
      AFTER INSERT,UPDATE
      AS
      BEGIN
      SET NOCOUNT ON;
      IF(select Interest_rate from inserted) <100
      print('not allowed')
      END

      Reply
  • Help Plz..

    Well hello there.. i am facing problem in updating using trigger

    following the trigger..

    well my requirements is to update the column . I want to store(update) the prev. value while updating in another columns correspond to id.

    like prevsal and newsalary.

    following trigger which i have created…

    *as we know when we update then (inserted and deleted will fire also in which the old value which we can retrieve using (select * from deleted))

    ALTER trigger [dbo].[tb] on [dbo].[tb_name]
    for insert, update
    as

    declare @count int

    declare @loop int

    declare @empid int

    select @loop =1

    select @count = (select * from tblname)

    WHILE(@loop <= @count)

    update tblname set newsalary=1200 where id=@loop and date='12/12/2010'

    set @loop= @loop+1

    update tblname set prevsal=(select newsalary from deleted)

    End

    *if there any Error then let me know well i am updating the field with column name newsalary but for column name prevsal they givng me NULL value except old value..

    hope you can understand my requirement

    with regards

    vikram

    Reply
  • Hi Phillip,

    I am trying to create a simple DDL trigger on server level, everything is fine on my local machine but when I am trying on our server (SQL 2005 Clustered) I am getting Error, any idea?

    Create TRIGGER test
    ON ALL SERVER
    FOR DDL_DATABASE_LEVEL_EVENTS
    AS
    BEGIN
    print ‘Hello’
    END

    Msg 1098, Level 15, State 1, Procedure test, Line 7
    The specified event type(s) is/are not valid on the specified target object.

    Reply
  • i want to execute the trigger whenever the new login is created i.e. new entry added to master..syslogins table ,

    Which server trigger i need to have and whats the synatx and minimum permissions needed to create it , now i am security admin.

    Reply
  • Hello.
    Is it possible to create a trigger to insert, update and delete data from all tables of current database?

    Something like this:

    CREATE TRIGGER myTrigger
    ON
    FOR INSERT, UPDATE, DELETE
    AS IF (@@ERROR 0)
    BEGIN
    INSERT into #tableSomething VALUES (1)
    END

    I’m using SQL Server 2008.
    Thank you
    TAP

    Reply
  • Great stuff

    Do you have any way to get alerted (paged/emailed) whenever a database has an Auto_grow?

    Reply
  • Hello sir,
    If I created a ddl trigger. How can i drop it??

    Reply
  • I m working on Trigger which will prevent database users from executing update statement on table.
    Sir, I Want to hide error 3609 after execution of Trigger.

    Reply
  • while trying to drop stored procs, error:

    Dropping Procedure is not allowed. DDL Trigger is preventing this from happening. To drop stored procedure run following script.

    Kindly help me….

    Reply
  • I m beginner in triggers i got this error..
    plz help me..

    thank u..

    CREATE TRIGGER preventDB
    ON DATABASE
    FOR DROP_PROCEDURE
    AS
    PRINT ‘HELLO’
    ROLLBACK
    GO

    error is…
    Incorrect syntax near the keyword ‘DATABASE’.

    Reply
  • Dear Pinal Dave,

    Where this magic tables are created?
    When they will created?
    When they will dropped?
    How long it will be used?

    Thanks,
    Prakash.

    Reply
  • shreyansh sahay
    May 13, 2018 1:59 pm

    Can we access the view after the table on which it has been created is dropped ?

    Reply
  • I created a DDL trigger which prevents adding new file to the database , After creating the trigger i try to alter the database by adding a new file , The output message says ” The transaction ended in trigger , the batch has been aborted” But still i see the new file added to the database , please share your valuable suggestion to make my trigger work.

    DDL Trigger to Prevent adding new file to database:

    CREATE TRIGGER prevent_addfile
    ON ALL SERVER
    FOR ALTER_DATABASE
    AS
    BEGIN
    PRINT ‘ALTER_DATABASE Issued.’

    DECLARE @EventData XML,
    @TSQL varchar(2000)
    SET @EventData=EVENTDATA()

    –SELECT @TSQL = @EventData.value(‘(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]’, ‘nvarchar(max)’)
    –PRINT @TSQL
    –IF CHARINDEX (‘ADD’,@TSQL) > 0
    –if( isnull(charindex(‘ADD’,@TSQL), 0) > 0 )

    IF @EventData.value(‘(/EVENT_INSTANCE/DatabaseName)[1]’, ‘varchar(50)’) LIKE ‘%dba%’
    AND @EventData.value(‘(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]’, ‘nvarchar(max)’) LIKE ‘%ADD%’

    RAISERROR (‘dbfile cannot be modified’, 16, 1)
    ROLLBACK
    END
    GO

    Output message after triggering the Alter database command on the “dba” database

    ALTER_DATABASE Issued.
    Msg 50000, Level 16, State 1, Procedure prevent_addfile, Line 21
    dbfile cannot be modified
    Msg 3609, Level 16, State 2, Line 1
    The transaction ended in the trigger. The batch has been aborted.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.