динамическая ошибка sql: "CREATE TRIGGER" должен быть первым оператором в пакете запросов

в рамках некоторых административных задач у нас есть много таблиц, каждая из которых нуждается в создании триггера. Триггер установит флаг и дату в базе данных аудита, когда объект был изменен. Для простоты у меня есть таблица со всеми объектами, которые нужно создать триггеры.

Я пытаюсь создать динамический sql, чтобы сделать это для каждого объекта, но я получаю эту ошибку:
'CREATE TRIGGER' must be the first statement in a query batch.

вот код для создания sql.

CREATE PROCEDURE [spCreateTableTriggers]
AS

BEGIN

DECLARE @dbname     varchar(50),
        @schemaname varchar(50),
        @objname    varchar(150),
        @objtype    varchar(150),
        @sql        nvarchar(max),
        @CRLF       varchar(2)

SET     @CRLF = CHAR(13) + CHAR(10);

DECLARE ObjectCursor CURSOR FOR
SELECT  DatabaseName,SchemaName,ObjectName
FROM    Audit.dbo.ObjectUpdates;

SET NOCOUNT ON;

OPEN    ObjectCursor ;

FETCH NEXT FROM ObjectCursor
INTO    @dbname,@schemaname,@objname;

WHILE @@FETCH_STATUS=0
BEGIN

    SET @sql = N'USE '+QUOTENAME(@dbname)+'; '
    SET @sql = @sql + N'IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'''+QUOTENAME(@schemaname)+'.[Tiud_'+@objname+'_AuditObjectUpdates]'')) '
    SET @sql = @sql + N'BEGIN DROP TRIGGER '+QUOTENAME(@schemaname)+'.[Tiud_'+@objname+'_AuditObjectUpdates]; END; '+@CRLF
    SET @sql = @sql + N'CREATE TRIGGER '+QUOTENAME(@schemaname)+'.[Tiud_'+@objname+'_AuditObjectUpdates] '+@CRLF
    SET @sql = @sql + N'   ON '+QUOTENAME(@schemaname)+'.['+@objname+'] '+@CRLF
    SET @sql = @sql + N'   AFTER INSERT,DELETE,UPDATE'+@CRLF
    SET @sql = @sql + N'AS '+@CRLF
    SET @sql = @sql + N'IF EXISTS(SELECT * FROM Audit.dbo.ObjectUpdates WHERE DatabaseName = '''+@dbname+''' AND ObjectName = '''+@objname+''' AND RequiresUpdate=0'+@CRLF
    SET @sql = @sql + N'BEGIN'+@CRLF
    SET @sql = @sql + N'    SET NOCOUNT ON;'+@CRLF
    SET @sql = @sql + N'    UPDATE  Audit.dbo.ObjectUpdates'+@CRLF
    SET @sql = @sql + N'    SET RequiresUpdate = 1'+@CRLF
    SET @sql = @sql + N'    WHERE   DatabaseName = '''+@dbname+''' '+@CRLF
    SET @sql = @sql + N'        AND ObjectName = '''+@objname+''' '+@CRLF

    SET @sql = @sql + N'END' +@CRLF
    SET @sql = @sql + N'ELSE' +@CRLF
    SET @sql = @sql + N'BEGIN' +@CRLF
    SET @sql = @sql + N'    SET NOCOUNT ON;' +@CRLF
    SET @sql = @sql + @CRLF
    SET @sql = @sql + N'    -- Update ''SourceLastUpdated'' date.'+@CRLF
    SET @sql = @sql + N'    UPDATE  Audit.dbo.ObjectUpdates'+@CRLF
    SET @sql = @sql + N'    SET SourceLastUpdated = GETDATE() '+@CRLF
    SET @sql = @sql + N'    WHERE   DatabaseName = '''+@dbname+''' '+@CRLF
    SET @sql = @sql + N'        AND ObjectName = '''+@objname+''' '+@CRLF
    SET @sql = @sql + N'END; '+@CRLF

    --PRINT(@sql);
    EXEC sp_executesql @sql;

    FETCH NEXT FROM ObjectCursor
    INTO    @dbname,@schemaname,@objname;

END

CLOSE ObjectCursor ;
DEALLOCATE ObjectCursor ;

END

если Я использую PRINT и вставить код в новое окно запроса, код выполняется без каких-либо проблем.

Я удалил GO заявления, поскольку это также давало ошибки.

что я упустил?
Почему я получаю ошибку, используя EXEC(@sql); или даже EXEC sp_executesql @sql;?
Это как-то связано с контекстом EXEC()?
Большое спасибо за любую помощь.

2 ответов


если вы используете SSMS (или другой подобный инструмент) для запуска кода, созданного этой скрипт, вы получите точно такую же ошибку. Он может работать нормально, когда вы вставили разделители пакетов (GO), но теперь, когда вы этого не сделаете, вы столкнетесь с той же проблемой в SSMS.

С другой стороны, причина, почему вы не можете поставить GO в ваших динамических скриптах это потому, что GO не является оператором SQL, это просто разделитель, распознанный SSMS и некоторыми другими инструментами. Возможно вы уже знаете об этом.

в любом случае, точки GO для инструмента, чтобы знать, что код должен быть разделен и его части работать отдельно. И это,отдельно, это то, что вы должны сделать в своем коде.

Итак, у вас есть следующие варианты:

  • вставить EXEC sp_execute @sql сразу после части, которая сбрасывает триггер, затем сбросьте значение @sql затем сохранить и запустить часть определения в его свою очередь;

  • использовать две переменные, @sql1 и @sql2, сохранить если существует / падение часть в @sql1 создать триггер в @sql2, затем запустить оба скрипта (опять же, отдельно).

но тогда, как вы уже выяснили, вы столкнетесь с другой проблемой: вы не можете создать триггер в другой базе данных без запуска оператора в контексте этой базы данных.

теперь, 2 пути обеспечивать необходимый контекст:

1) использовать USE отчетность;

2) Запустите инструкцию(ы) как динамический запрос, используя EXEC targetdatabase..sp_executesql N'…'.

очевидно, что первый вариант здесь не работает: мы не можем добавить USE … до CREATE TRIGGER, потому что последний должен быть единственным оператором в пакете.

второй вариант can используется, но для этого потребуется дополнительный слой динамичность (не уверен, что это слово). Это потому что имя базы данных является параметром здесь, и поэтому нам нужно запустить EXEC targetdatabase..sp_executesql N'…' as динамический скрипт, и поскольку фактический скрипт для запуска сам по себе должен быть динамическим скриптом, он, следовательно, будет вложен дважды.

Итак, перед (второй) EXEC sp_executesql @sql; строка добавить следующее:

SET @sql = N'EXEC ' + @dbname + '..sp_executesql N'''
           + REPLACE(@sql, '''', '''''') + '''';

как вы можете видеть, интегрировать содержание @sql как вложенный динамический скрипт, они должны быть заключены в одинарные кавычки. По той же причине, каждый одинарная кавычка на @sql должно быть удвоено (например, с помощью REPLACE() функции, как и в приведенном выше утверждении).


Создание триггера должно выполняться в собственном пакете выполнения. Вы находитесь внутри процедуры, поэтому вы не сможете ее создать.

Я предлагаю добавить @sql в временную таблицу, а затем, как только proc закончит генерировать все операторы, зациклите эту временную таблицу, чтобы выполнить их и создать триггеры