2012-04-26 4 views
7

В рамках некоторых административных задач у нас есть много таблиц, каждая из которых требует создания триггера. Триггер установит флаг и дату в базе данных аудита, когда объект был изменен. Для простоты у меня есть таблица со всеми объектами, для которых созданы триггеры.динамическая ошибка 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_'[email protected]+'_AuditObjectUpdates]'')) ' 
    SET @sql = @sql + N'BEGIN DROP TRIGGER '+QUOTENAME(@schemaname)+'.[Tiud_'[email protected]+'_AuditObjectUpdates]; END; '[email protected] 
    SET @sql = @sql + N'CREATE TRIGGER '+QUOTENAME(@schemaname)+'.[Tiud_'[email protected]+'_AuditObjectUpdates] '[email protected] 
    SET @sql = @sql + N' ON '+QUOTENAME(@schemaname)+'.['[email protected]+'] '[email protected] 
    SET @sql = @sql + N' AFTER INSERT,DELETE,UPDATE'[email protected] 
    SET @sql = @sql + N'AS '[email protected] 
    SET @sql = @sql + N'IF EXISTS(SELECT * FROM Audit.dbo.ObjectUpdates WHERE DatabaseName = '''[email protected]+''' AND ObjectName = '''[email protected]+''' AND RequiresUpdate=0'[email protected] 
    SET @sql = @sql + N'BEGIN'[email protected] 
    SET @sql = @sql + N' SET NOCOUNT ON;'[email protected] 
    SET @sql = @sql + N' UPDATE Audit.dbo.ObjectUpdates'[email protected] 
    SET @sql = @sql + N' SET RequiresUpdate = 1'[email protected] 
    SET @sql = @sql + N' WHERE DatabaseName = '''[email protected]+''' '[email protected] 
    SET @sql = @sql + N'  AND ObjectName = '''[email protected]+''' '[email protected] 

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

    --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()?
Большое спасибо за любую помощь.

ответ

17

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

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

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

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

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

  • использовать две переменные, @sql1 и @sql2, хранить IF EXISTS/DROP часть в @sql1, то CREATE TRIGGER один в @sql2, а затем запустить оба сценария (опять же, отдельно).

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

Теперь есть 2 способа предоставления необходимого контекста:

1) Используйте USE заявление;

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

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

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

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

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

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

+0

Большое спасибо за это. Теперь я разделил код на две части, как вы предлагаете в своем первом варианте выше, следующим образом: – MarkusBee

+0

[EDIT приурочен к предыдущему комментарию.] Большое спасибо. Я разделил код на две части, как вы предлагаете в своем первом варианте. Первая часть выполняется отлично. Я поясню, что процедура выполняется из базы данных «Аудит», а объекты, требующие триггеры, находятся в других базах данных. Вывод выражения 'CREATE TRIGGER' теперь вызывает следующую ошибку, даже если используется полное имя таблицы: « Невозможно создать триггер на [...], поскольку цель не находится в текущей базе данных ». Есть ли способ обойти это? Как я могу заставить его выполнить в контексте другой базы данных? Спасибо. – MarkusBee

+0

@markb: см. Мое обновление. Я не уверен, все ли так ясно, как хотелось бы, поэтому, пожалуйста, не стесняйтесь спрашивать. –

0

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

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

Смежные вопросы