2012-04-26 27 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(或其他類似工具)運行這個腳本產生的代碼,你會得到完全一樣的錯誤。當你插入批量分隔符(GO)時它可以正常運行,但現在你不知道,你也會在SSMS中遇到同樣的問題。

另一方面,您不能將GO放入您的動態腳本中的原因是因爲GO不是SQL語句,它只是SSMS和其他工具識別的分隔符。可能你已經意識到了這一點。

無論如何,GO的要點是讓工具知道代碼應該拆分並且其零件分別運行。而且,分別爲,這也是您應該在代碼中執行的操作。

所以,你有以下選擇:

  • 插入EXEC sp_execute @sql那滴觸發部分剛過,然後重置的@sql值,以便然後存儲和運行反過來定義部分;

  • 使用兩個變量,@sql1@sql2,所述IF EXISTS/DROP部分TRIGGER一個存儲到@sql1,在CREATE成@sql2,然後運行這兩個腳本(再次,單獨)。

但後來,因爲你已經發現了,你要面對另一個問題:你不能沒有在數據庫的上下文中運行的聲明建立在另一個數據庫的觸發器。

現在,有提供必要的上下文的2種方式:

1)使用USE聲明;

2)運行如使用EXEC targetdatabase..sp_executesql N'…'動態查詢語句(多個)。

顯然,第一個選項不會在這裏工作:我們不能在CREATE TRIGGER之前添加USE …,因爲後者必須是批處理中唯一的語句。

第二個選項可以可以使用,但它需要動態性(不知道這是否是一個字)的附加層。這是因爲該數據庫名是一個參數在這裏,所以我們需要運行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 2012-04-27 08:26:23

+0

非常感謝。如您在第一個選項中所建議的那樣,我已將代碼分成兩部分。第一部分執行完美。 我將闡明該程序是從「審計」數據庫執行的,而需要觸發器的對象位於其他數據庫中。 現在Excecuting了'CREATE語句TRIGGER'引發以下錯誤,即使使用完全限定表名時: 「無法創建[...]作爲目標不在當前數據庫觸發器」。 有沒有辦法解決這個問題?我怎樣才能讓它在另一個數據庫的上下文中執行? 謝謝。 – MarkusBee 2012-04-27 08:37:51

+0

@markb:請參閱我的更新。我不確定是否一切都如我所願,因此請不要猶豫,問。 – 2012-04-27 09:33:07

0

觸發器創建必須在其自己的執行批次上完成。你在一個過程中,所以你不能創建它。

我建議增加@sql到一個臨時表,然後一旦proc已經完成生成所有語句,循環這個臨時表來執行它們,並創建觸發器

相關問題