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

时间:2012-04-26 15:18:20

标签: sql-server-2008 triggers dynamic-sql

作为一些管理任务的一部分,我们有许多表,每个表都需要创建一个触发器。触发器将在修改对象时在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

如果我使用PRINT并将代码粘贴到新的查询窗口,代码就会毫无问题地执行。

我删除了GO语句,因为这也是错误。

我缺少什么?
为什么我使用EXEC(@sql);甚至EXEC sp_executesql @sql;收到错误? 这与EXEC()中的背景有关吗? 非常感谢您的帮助。

2 个答案:

答案 0 :(得分:17)

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

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

无论如何,GO的要点是让工具知道应该拆分代码并且其部分分别运行 。那个,单独,也是你应该在你的代码中做的。

所以,你有这些选择:

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

  • 使用两个变量@sql1@sql2,将IF EXISTS / DROP部分存储到@sql1,将CREATE 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, '''', '''''') + '''';

如您所见,要将@sql的内容正确地集成为嵌套动态脚本,必须将它们括在单引号中。出于同样的原因, @sql中的每个引号都必须加倍(例如使用REPLACE() function,如上所述。)

答案 1 :(得分:0)

必须在自己的执行批处理上创建触发器。你在一个程序中,所以你不能创建它。

我建议将@sql添加到临时表中,然后一旦proc完成生成所有语句,循环此临时表以执行它们并创建触发器