34

I've got the following trigger on a table for a SQL Server 2008 database. It's recursing, so I need to stop it.

After I insert or update a record, I'm trying to simply update a single field on that table.

Here's the trigger :

ALTER TRIGGER [dbo].[tblMediaAfterInsertOrUpdate] 
   ON  [dbo].[tblMedia]
   BEFORE INSERT, UPDATE
AS 
BEGIN
    SET NOCOUNT ON

    DECLARE @IdMedia INTEGER,
        @NewSubject NVARCHAR(200)   

    SELECT @IdMedia = IdMedia, @NewSubject = Title
    FROM INSERTED

    -- Now update the unique subject field.
    -- NOTE: dbo.CreateUniqueSubject is my own function. 
    --       It just does some string manipulation.
    UPDATE tblMedia
    SET UniqueTitle = dbo.CreateUniqueSubject(@NewSubject) + 
                      CAST((IdMedia) AS VARCHAR(10))
    WHERE tblMedia.IdMedia = @IdMedia
END

Can anyone tell me how I can prevent the trigger's insert from kicking off another trigger again?

D Stanley
  • 149,601
  • 11
  • 178
  • 240
Pure.Krome
  • 84,693
  • 113
  • 396
  • 647
  • 2
    A number of people have said to disable trigger recursion. right now, i'm not going to want to touch that setting. I prefer to fix up the tsql. – Pure.Krome Oct 07 '09 at 06:01
  • 1
    Then peraps the trigger should not be BEFORE but an INSTEAD OF trigger? http://msdn.microsoft.com/en-us/library/ms175089.aspx – Remus Rusanu Oct 07 '09 at 21:43
  • An instead of update trigger, still would require an update that would still cause recursion. – Triynko May 06 '19 at 19:44
  • Only disable trigger recursion if you are 100% sure that you won't need other triggers to recurse in the future. (Hint: you're not.) – Denziloe Oct 03 '19 at 09:30

7 Answers7

77

Not sure if it is pertinent to the OP's question anymore, but in case you came here to find out how to prevent recursion or mutual recursion from happening in a trigger, you can test for this like so:

IF TRIGGER_NESTLEVEL() <= 1/*this update is not coming from some other trigger*/

MSDN link

Anssssss
  • 3,087
  • 31
  • 40
  • 7
    This is actually the best answer, and the one that most directly answers the poster's question. – Curt Aug 05 '16 at 20:52
  • 4
    As the answer does actually state, this also prevents the trigger firing if the update is coming from some other trigger. But that is not recursion. Recursion occurs when the update comes form the same trigger. In that case you need pass the object id to the function: https://stackoverflow.com/a/47074365/150342 – Colin Nov 02 '17 at 11:43
  • I know this is an older question and answer, but would this be the equivalent to `pg_trigger_depth()` for postgres? – Int'l Man Of Coding Mystery Oct 03 '19 at 07:17
35

I see three possibilities:

  1. Disable trigger recursion:

    This will prevent a trigger fired to call another trigger or calling itself again. To do this, execute this command:

    ALTER DATABASE MyDataBase SET RECURSIVE_TRIGGERS OFF
    GO
    
  2. Use a trigger INSTEAD OF UPDATE, INSERT

    Using a INSTEAD OF trigger you can control any column being updated/inserted, and even replacing before calling the command.

  3. Control the trigger by preventing using IF UPDATE

    Testing the column will tell you with a reasonable accuracy if you trigger is calling itself. To do this use the IF UPDATE() clause like:

    ALTER TRIGGER [dbo].[tblMediaAfterInsertOrUpdate]
       ON  [dbo].[tblMedia]
       FOR INSERT, UPDATE
    AS
    BEGIN
        SET NOCOUNT ON
        DECLARE @IdMedia INTEGER,
            @NewSubject NVARCHAR(200)   
    
        IF UPDATE(UniqueTitle)
          RETURN;
    
        -- What is the new subject being inserted?
        SELECT @IdMedia = IdMedia, @NewSubject = Title
        FROM INSERTED
    
        -- Now update the unique subject field.
        -- NOTE: dbo.CreateUniqueSubject is my own function. 
        --       It just does some string manipulation.
        UPDATE tblMedia
        SET UniqueTitle = dbo.CreateUniqueSubject(@NewSubject) + 
                          CAST((IdMedia) AS VARCHAR(10))
        WHERE tblMedia.IdMedia = @IdMedia
    END
    
TT.
  • 15,774
  • 6
  • 47
  • 88
Rodrigo
  • 4,365
  • 3
  • 31
  • 49
  • Quick question. U're using _BEFORE_ instead of _AFTER_. Will this still give me the new Inserted (Identity) ID value? Or does that only get created on an _AFTER_ ?? – Pure.Krome Oct 07 '09 at 04:45
  • Nope - not working. I'm doing an insert but the Update(UniqueTitle) must think it's an update... ??? – Pure.Krome Oct 07 '09 at 05:57
  • More on 'IF UPDATE(..)' :: http://msdn.microsoft.com/en-us/library/aa258254%28SQL.80%29.aspx .. Quote => "ests for an INSERT or UPDATE action to a specified column.." :( no can do, then. – Pure.Krome Oct 07 '09 at 06:00
  • The UPDATE(Column) will be true if a value have been set for the column in the instruction that "triggered" the trigger. – Rodrigo Oct 11 '09 at 14:27
  • This is a terrible way to write a trigger. Triggers work on *sets* (INSERTED/DELETED), so any trigger that gets the first value in the set is likely *broken*. A valid trigger usually follows a form like `UPDATE t SET .. FROM table t JOIN INSERTED i ON ..` or similar, leveraging joins (and set operations). – user2864740 Dec 21 '20 at 21:42
23

TRIGGER_NESTLEVEL can be used to prevent recursion of a specific trigger, but it is important to pass the object id of the trigger into the function. Otherwise you will also prevent the trigger from firing when an insert or update is made by another trigger:

   IF TRIGGER_NESTLEVEL(OBJECT_ID('dbo.mytrigger')) > 1
         BEGIN
             PRINT 'mytrigger exiting because TRIGGER_NESTLEVEL > 1 ';
             RETURN;
     END;

From MSDN:

When no parameters are specified, TRIGGER_NESTLEVEL returns the total number of triggers on the call stack. This includes itself.

Reference: Avoiding recursive triggers

Colin
  • 22,328
  • 17
  • 103
  • 197
  • 1
    I find this is the most useful answer since I do want other triggers to trigger this trigger, which is not recursion. – Tobias Feil Apr 05 '19 at 07:16
  • 5
    Consider `TRIGGER_NESTLEVEL(@@PROCID)` as a more generic check - "[[@@PROCID returns the](https://learn.microsoft.com/en-us/sql/t-sql/functions/procid-transact-sql?view=sql-server-ver15)] object identifier (ID) of the current Transact-SQL module. A Transact-SQL module can be a stored procedure, user-defined function, _or trigger_." – user2864740 Dec 21 '20 at 21:21
  • 1
    @user2864740 surely when trying to prevent _recursion_ you want the check to be specific rather than generic? – Colin Dec 22 '20 at 08:30
  • 2
    In this case the “specific” and the generic are the *same* with @@PROCID vs the OBJECT_ID and the name of the same trigger (if checking a different trigger such is simply not relevant). If one wanted to be more specific, pass the 2nd and 3rd arguments to TRIGGER_NESTLEVEL. – user2864740 Dec 23 '20 at 01:06
  • 3
    @user2864740 Using `@@PROCID` to avoid the magic string `'dbo.mytrigger'` seems to have merit. Is that what you were getting at with the term "generic"? Your subsequent comments about SPs and UDFs make it sound like you are trying to guard against the nonexistent problem of a trigger being an SP or UDF rather than a trigger. – MarredCheese Feb 13 '21 at 20:30
9
ALTER DATABASE <dbname> SET RECURSIVE_TRIGGERS OFF

RECURSIVE_TRIGGERS { ON | OFF }

ON Recursive firing of AFTER triggers is allowed.

OFF Only direct recursive firing of AFTER triggers is not allowed. To also disable indirect recursion of AFTER triggers, set the nested triggers server option to 0 by using sp_configure.

Only direct recursion is prevented when RECURSIVE_TRIGGERS is set to OFF. To disable indirect recursion, you must also set the nested triggers server option to 0.

The status of this option can be determined by examining the is_recursive_triggers_on column in the sys.databases catalog view or the IsRecursiveTriggersEnabled property of the DATABASEPROPERTYEX function.

Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
  • 2
    To check the setting: `SELECT is_recursive_triggers_on FROM sys.databases WHERE name = 'YourDatabaseName'` – Elaskanator May 24 '18 at 19:48
  • 2
    This is a bad solution. You shouldn't globally disable functionality just to solve an immediate problem. It could break existing code and it could make future coding much harder. – Denziloe Oct 03 '19 at 09:33
7

I think i got it :)

When the title is getting 'updated' (read: inserted or updated), then update the unique subject. When the trigger gets ran a second time, the uniquesubject field is getting updated, so it stop and leaves the trigger.

Also, i've made it handle MULTIPLE rows that get changed -> I always forget about this with triggers.

ALTER TRIGGER [dbo].[tblMediaAfterInsert] 
   ON  [dbo].[tblMedia]
   FOR INSERT, UPDATE
AS 
BEGIN
    SET NOCOUNT ON

    -- If the Title is getting inserted OR updated then update the unique subject.
    IF UPDATE(Title) BEGIN
        -- Now update all the unique subject fields that have been inserted or updated.
        UPDATE tblMedia 
        SET UniqueTitle = dbo.CreateUniqueSubject(b.Title) + 
                          CAST((b.IdMedia) AS VARCHAR(10))
        FROM tblMedia a
            INNER JOIN INSERTED b on a.IdMedia = b.IdMedia
    END
END
Pure.Krome
  • 84,693
  • 113
  • 396
  • 647
1

You can have a separate NULLABLE column indicating whether the UniqueTitle was set.

Set it to true value in a trigger, and have the trigger do nothing if it's value is true in "INSERTED"

DVK
  • 126,886
  • 32
  • 213
  • 327
1

For completeness sake, I will add a few things. If you have a particular after trigger that you only want to run once, you can set it up to run last using sp_settriggerorder.

I would also consider if it might not be best to combine the triggers that are doing the recursion into one trigger.

HLGEM
  • 94,695
  • 15
  • 113
  • 186