作为一些管理任务的一部分,我们有许多表,每个表都需要创建一个触发器。触发器将在修改对象时在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()
中的背景有关吗?
非常感谢您的帮助。
答案 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完成生成所有语句,循环此临时表以执行它们并创建触发器