2012-04-26 71 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

を作成するために、ループ、そしてこの一時テーブルを一時テーブルに@sqlを追加することを提案し、ツール)を使用してスクリプトで生成されたコードを実行すると、まったく同じエラーが発生します。バッチ区切り記号(GO)を挿入しても問題なく実行できますが、そうしないとSSMSでも同じ問題に直面します。

GOを動的スクリプトに入れることができない理由は、GOはSQL文ではないため、SSMSやその他のツールで認識される区切り文字に過ぎません。おそらくあなたはすでにそれを認識しています。

とにかく、GOのポイントは、コードを分割してその部分をと別々に実行することをツールが認識するためのものです。そして、と別々にがあなたのコードでもすべきことです。

だから、あなたはこれらのオプションがあります。

  • はただトリガーをドロップ部分の後に、その後、その順番に定義部分を保存して実行する@sqlの値をリセットEXEC sp_execute @sqlを挿入します。 2つの変数@sql1@sql2は、@sql1にIFがEXISTS/DROPの一部を格納

  • 使用、(別々に、再び)の両方のスクリプトを実行し、@sql2にTRIGGERを作成。あなたは既に出て見つけたとして

しかし、その後、あなたは別の問題に直面します:あなたは、そのデータベースのコンテキストで声明を実行せずに、別のデータベース内にトリガーを作成することはできません。

今、必要なコンテキストを提供する2つの方法があります:

1)USEステートメントを使用します。

2)EXEC targetdatabase..sp_executesql N'…'を使用して動的クエリとしてステートメントを実行します。

明らかに、最初のオプションはここでは機能しません。CREATE TRIGGERの前にUSE …を追加することはできません。なぜなら、後者はバッチ内の唯一のステートメントでなければならないからです。

番目のオプション使用することができますが、それは動的性(それは言葉だかどうかわからない)の追加の層が必要になります。データベース名はここではパラメータであるため、EXEC targetdatabase..sp_executesql N'…'として実行する必要があります。実行する実際のスクリプト自体は動的スクリプトであるため、2回ネストされます。

ので、(二)EXEC sp_executesql @sql;行は次のように追加する前に:

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

あなたが見ることができるように、適切にネストされた動的なスクリプトとして@sqlの内容を統合するために、彼らは、単一引用符で囲む必要があります。同じ理由で、@sqlは、上記のステートメントのようにREPLACE() functionを使用して2倍にする必要があります。

+0

これは本当にありがとうございます。上記の最初のオプションで、次のようにコードを2つに分割しました: – MarkusBee

+0

[編集前のコメントにタイムアウトしました] ありがとうございました。私はあなたの最初のオプションで示唆しているように、コードを2つの '部分'に分割しました。最初の部分は完全に実行されます。 この手順は「監査」データベースから実行され、トリガーを必要とするオブジェクトは他のデータベースにあることを明確にします。 完全修飾テーブル名を使用していても、CREATE TRIGGERステートメントを実行すると、次のエラーがスローされるようになりました: "ターゲットが現在のデータベースにないため、[...]でトリガーを作成できません。 これを回避する方法はありますか?別のデータベースのコンテキスト内で実行するにはどうすればよいですか?おかげさまで – MarkusBee

+0

@markb:私の更新を見てください。私はそれが欲しいと思うほどすべてが明確であるかどうか分からないので、躊躇しないでください。 –

0

トリガー作成は、独自の実行バッチで行う必要があります。あなたはプロシージャの中にいるので、それを作成することはできません。

procは文の全てを生成仕上げになると私はあなたがSSMS(または他の類似を使用している場合は、それらを実行し、トリガに

関連する問題