Как предотвратить повторение триггера базы данных?

У меня есть следующий триггер в таблице для базы данных SQL Server 2008. Это повторяется, поэтому мне нужно остановить это.

После вставки или обновления записи я пытаюсь просто обновить одно поле в этой таблице.

Вот триггер:

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

Может ли кто-нибудь сказать мне, как я могу предотвратить повторный запуск другого триггера вставкой триггера?


person Pure.Krome    schedule 07.10.2009    source источник
comment
Некоторые люди сказали отключить рекурсию триггера. прямо сейчас я не хочу трогать эту настройку. Я предпочитаю исправить файл tsql.   -  person Pure.Krome    schedule 07.10.2009
comment
Тогда, возможно, триггер должен быть не BEFORE, а триггером INSTEAD OF? msdn.microsoft.com/en-us/library/ms175089.aspx   -  person Remus Rusanu    schedule 08.10.2009
comment
Вместо триггера обновления все равно потребуется обновление, которое все равно вызовет рекурсию.   -  person Triynko    schedule 06.05.2019
comment
Отключайте рекурсию триггера только в том случае, если вы на 100% уверены, что вам не понадобятся другие триггеры для рекурсии в будущем. (Подсказка: это не так.)   -  person Denziloe    schedule 03.10.2019


Ответы (7)


Не уверен, что это больше относится к вопросу OP, но если вы пришли сюда, чтобы узнать, как предотвратить рекурсию или взаимную рекурсию в триггере, вы можете проверить это следующим образом:

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

ссылка

person Anssssss    schedule 01.06.2015
comment
На самом деле это лучший ответ, и тот, который наиболее прямо отвечает на вопрос автора. - person Curt; 05.08.2016
comment
Как на самом деле говорится в ответе, это также предотвращает срабатывание триггера, если обновление исходит от какого-либо другого триггера. Но это не рекурсия. Рекурсия возникает, когда обновление поступает из одного и того же триггера. В этом случае вам нужно передать идентификатор объекта в функцию: stackoverflow.com/a/47074365/150342 - person Colin; 02.11.2017
comment
Я знаю, что это старый вопрос и ответ, но будет ли это эквивалентно pg_trigger_depth() для postgres? - person Int'l Man Of Coding Mystery; 03.10.2019

Я вижу три возможности:

  1. Отключить рекурсию триггера:

    Это предотвратит запуск триггера для вызова другого триггера или повторного вызова самого себя. Для этого выполните эту команду:

    ALTER DATABASE MyDataBase SET RECURSIVE_TRIGGERS OFF
    GO
    
  2. Используйте триггер ВМЕСТО ОБНОВЛЕНИЯ, ВСТАВЬТЕ

    С помощью триггера INSTEAD OF вы можете управлять обновлением/вставкой любого столбца и даже заменой перед вызовом команды.

  3. Управлять триггером, запрещая использование IF UPDATE

    Тестирование столбца покажет вам с разумной точностью, вызывает ли ваш триггер сам себя. Для этого используйте предложение IF UPDATE(), например:

    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
    
person Rodrigo    schedule 07.10.2009
comment
Быстрый вопрос. Вы используете ДО вместо ПОСЛЕ. Будет ли это по-прежнему давать мне новое значение идентификатора Inserted (Identity)? Или это создается только в ПОСЛЕ ?? - person Pure.Krome; 07.10.2009
comment
Нет - не работает. Я делаю вставку, но Update(UniqueTitle) должен думать, что это обновление... ??? - person Pure.Krome; 07.10.2009
comment
Подробнее об 'IF UPDATE(..)' :: msdn.microsoft.com/en-us/library/aa258254%28SQL.80%29.aspx .. Quote => ests для действия INSERT или UPDATE в указанном столбце.. :( нет может сделать, тогда . - person Pure.Krome; 07.10.2009
comment
UPDATE(столбец) будет истинным, если для столбца было установлено значение в инструкции, которая активировала триггер. - person Rodrigo; 11.10.2009
comment
Это ужасный способ написания триггера. Триггеры работают с наборами (INSERTED/DELETED), поэтому любой триггер, который получает первое значение в наборе, скорее всего неисправен. Действительный триггер обычно следует форме, такой как UPDATE t SET .. FROM table t JOIN INSERTED i ON .. или аналогичной, используя соединения (и операции над установками). - person user2864740; 22.12.2020

TRIGGER_NESTLEVEL можно использовать для предотвращения рекурсии определенного триггера, но важно передать идентификатор объекта триггера в функцию. В противном случае вы также предотвратите срабатывание триггера, когда вставка или обновление выполняется другим триггером:

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

Из MSDN:

Если параметры не указаны, TRIGGER_NESTLEVEL возвращает общее количество триггеров в стеке вызовов. Это включает себя.

Ссылка: Избегание рекурсивных триггеров< /а>

person Colin    schedule 02.11.2017
comment
Я считаю, что это самый полезный ответ, поскольку я хочу, чтобы другие триггеры запускали этот триггер, а не рекурсию. - person Tobias Feil; 05.04.2019
comment
Считайте TRIGGER_NESTLEVEL(@@PROCID) более общей проверкой — [@@PROCID возвращает] идентификатор объекта (ID) текущего модуля Transact-SQL. Модуль Transact-SQL может быть хранимой процедурой, определяемой пользователем функцией или триггером. - person user2864740; 22.12.2020
comment
@user2864740 user2864740, конечно, при попытке предотвратить рекурсию вы хотите, чтобы проверка была конкретной, а не общей? - person Colin; 22.12.2020
comment
В этом случае «конкретный» и универсальный — это одно и то же с @@PROCID по сравнению с OBJECT_ID и именем одного и того же триггера (если проверка другого триггера просто не имеет значения). Если вы хотите быть более конкретным, передайте 2-й и 3-й аргументы в TRIGGER_NESTLEVEL. - person user2864740; 23.12.2020
comment
@user2864740 user2864740 Использование @@PROCID, чтобы избежать волшебной строки 'dbo.mytrigger', похоже, имеет смысл. Это то, что вы имели в виду с термином общий? Ваши последующие комментарии о SP и UDF звучат так, как будто вы пытаетесь защититься от несуществующей проблемы триггера, являющегося SP или UDF, а не триггером. - person MarredCheese; 13.02.2021

ALTER DATABASE <dbname> SET RECURSIVE_TRIGGERS OFF

RECURSIVE_TRIGGERS { ON | ВЫКЛ.

ON Рекурсивное срабатывание триггеров AFTER разрешено.

OFF Не допускается только прямое рекурсивное срабатывание триггеров AFTER. Чтобы также отключить непрямую рекурсию триггеров AFTER, установите для параметра сервера вложенных триггеров значение 0 с помощью процедуры sp_configure.

Только прямая рекурсия предотвращается, когда RECURSIVE_TRIGGERS установлен в OFF. Чтобы отключить непрямую рекурсию, необходимо также установить для параметра сервера вложенных триггеров значение 0.

Статус этого параметра можно определить, изучив столбец is_recursive_triggers_on в представлении каталога sys.databases или свойство IsRecursiveTriggersEnabled функции DATABASEPROPERTYEX.

person Remus Rusanu    schedule 07.10.2009
comment
Чтобы проверить настройку: SELECT is_recursive_triggers_on FROM sys.databases WHERE name = 'YourDatabaseName' - person Elaskanator; 24.05.2018
comment
Это плохое решение. Вы не должны глобально отключать функциональность только для решения неотложной проблемы. Это может сломать существующий код и значительно усложнить кодирование в будущем. - person Denziloe; 03.10.2019

Я думаю, я понял :)

Когда заголовок «обновляется» (читай: вставляется или обновляется), обновите уникальную тему. Когда триггер запускается во второй раз, поле uniquesubject обновляется, поэтому он останавливается и покидает триггер.

Кроме того, я заставил его обрабатывать НЕСКОЛЬКО строк, которые изменяются -> я всегда забываю об этом с триггерами.

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
person Pure.Krome    schedule 07.10.2009
comment
Я не уверен, почему за это кто-то проголосовал, это довольно полезное решение. - person MikeBeaton; 27.04.2016

У вас может быть отдельный столбец NULLABLE, указывающий, был ли установлен UniqueTitle.

Установите для него значение true в триггере, и пусть триггер ничего не делает, если его значение истинно в «INSERTED».

person DVK    schedule 07.10.2009

Для полноты добавлю несколько вещей. Если у вас есть определенный последующий триггер, который вы хотите запустить только один раз, вы можете настроить его так, чтобы он запускался последним, используя sp_settriggerorder.

Я бы также подумал, не лучше ли объединить триггеры, выполняющие рекурсию, в один триггер.

person HLGEM    schedule 23.03.2015