动态sql错误:'CREATE TRIGGER'必须是查询批处理中的第一个语句

Mar*_*Bee 8 triggers dynamic-sql sql-server-2008

作为一些管理任务的一部分,我们有许多表,每个表都需要创建一个触发器.触发器将在修改对象时在Audit数据库中设置标志和日期.为简单起见,我有一个表,其中包含所有需要触发器创建的对象.

我试图生成一些动态的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
Run Code Online (Sandbox Code Playgroud)

如果我使用PRINT并将代码粘贴到新的查询窗口,代码执行没有任何问题.

我删除了这些GO陈述,因为这也是错误的.

我错过了什么?
为什么我使用EXEC(@sql);甚至是错误EXEC sp_executesql @sql;
这与内部环境有关EXEC()吗?
非常感谢任何帮助.

And*_*y M 18

如果您使用SSMS(或其他类似工具)来运行脚本生成的代码,您将得到完全相同的错误.当你插入批量分隔符(GO)时,它可以正常运行,但是现在你没有,你也会在SSMS中遇到同样的问题.

另一方面,您不能放入GO动态脚本的原因GO是因为它不是SQL语句,它只是SSMS和其他一些工具识别的分隔符.可能你已经意识到了这一点.

无论如何,关键GO是工具要知道应该拆分代码并且它的部分单独运行.而这,分开,是你应该在你的代码做什么为好.

所以,你有这些选择:

  • 在插入EXEC sp_execute @sql触发器的部件之后插入,然后重置值,@sql然后存储并运行定义部件;

  • 使用两个变量,@sql1并将@sql2IF EXISTS/DROP部分存储到@sql1CREATE TRIGGER中@sql2,然后运行两个脚本(再次单独).

但是,正如您已经发现的那样,您将面临另一个问题:如果不在该数据库的上下文中运行该语句,则无法在另一个数据库中创建触发器.

现在,有两种提供必要背景的方法:

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, '''', '''''') + '''';
Run Code Online (Sandbox Code Playgroud)

如您所见,要@sql正确地将内容集成为嵌套动态脚本,必须将它们括在单引号中.出于同样的原因,每一个单引号 @sql必须翻倍(使用例如REPLACE()功能,如在上面的语句).