TSQL-BEGIN .. ENDブロック内でGOを使用する方法


96

複数の開発データベースからステージング/プロダクションに変更を自動的に移行するためのスクリプトを生成しています。基本的に、これは一連の変更スクリプトを受け取り、それらを単一のスクリプトにマージして、各スクリプトをIF whatever BEGIN ... ENDステートメントにラップします。

ただし、一部のスクリプトではGOステートメントが必要になるため、たとえば、SQLパーサーは、作成後に新しい列を認識します。

ALTER TABLE dbo.EMPLOYEE 
ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO -- Necessary, or next line will generate "Unknown column:  EMP_IS_ADMIN"
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever

しかし、それをIFブロックでラップすると:

IF whatever
BEGIN
    ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
    GO
    UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever
END

BEGIN一致しないを送信しているため、失敗しますEND。しかし、それを削除するGOと、不明な列について再び不平を言います。

単一のIFブロック内で同じ列を作成および更新する方法はありますか?


2
stackoverflow.com/questions/4855537/…を参照してください
GBN

2
@gbn:はい、これがなぜ起こるのか理解しています(2番目の段落を参照)。しかし、私はそれを回避する方法がわかりません-私は本当にすべてのクエリを文字列の束に変える必要がありますか?
BlueRaja-Danny Pflughoeft、2011年

@BlueRaja:何が心配ですか?うまくいけば、結局のところ、それだけで問題は解決します。提供されたソリューションに正当なビジネス上の問題がある場合は、それを表明してください。すべてのクエリを文字列の束に変換することについて特に戸惑うことはありますか?
mellamokb、2011年

1
@mellamokb:はい、問題があります。GOという単語が他のコンテキスト(コメントや文字列など)で使用されている場合、スクリプトは機能しません。また、問題が発生した場合に備えて、エラーメッセージ内の有用な行番号が失われます。トランザクションでこれを行う方法はありませんか?またはトライ/キャッチ?
BlueRaja-ダニープフルフフト

@BlueRaja:1)私はGOそれ自体が一列に並んでいる必要があると思うので、単語のすべてのインスタンスではなく、そのケースのみを検索できますGO。2)どのステートメントが正常に完了したかを常にログに記録できます。または、全体をtry / catchでラップし、追跡する@lineNoなどの変数を使用して独自の行番号を使用し、エラーを報告することもできます。これらを自動的に生成しているので、このような変更は簡単です。あなたのすべての懸念に対して解決策が見つかると私が思うとき、このルートを探索したくないのは明らかです。
mellamokb 2011年

回答:


44

私は同じ問題を抱えていて、ついにSET NOEXECを使用してそれを解決することができました。

IF not whatever
BEGIN
    SET NOEXEC ON; 
END

ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever

SET NOEXEC OFF; 

2
これは素晴らしいソリューションです!
Bazinga

+1!これは、ステートメント内のいくつかの呼び出しで他のSSスクリプト(つまりサブデプロイメントスクリプト)を(コマンドを介して)呼び出すSS モードスクリプト(つまりマスターデプロイメントスクリプト)で使用するための、これまでのところ唯一の実用的な回答です。Oded、mellamokb、およびAndyジョイナーのこれらすべてのステートメントをCalls / - で囲むことの回答は、スターターではありません。また、Statement がある場合、- メソッドは機能しません(たとえば、その前に明示が必要です)。しかし、男、「ホーリーダブルネガティブ、バットマン!」;)SQLCMD:rifexecbeginendbeginendcreatego
トム

読みやすさのために(たとえば、二重否定を克服し、仮想 ifブロックをシミュレートしていることを明確にするため)、ブロックの前に-- If whateverコメントを付け、ブロックをインデントし、コメントの後にブロックを--end If whatever後置します。
トム

あなたは私のベーコンを救った!私はマージステートメントを実行していましたが、これらのダムGOはIF BEGIN END ELSE
Omzig

ええと、noexec onが実行された後、どういうわけかアップデートでエラーが発生しますか?(更新する列名が無効であるというエラー)クエリエディターでMSSQL 2014を実行します。条件がfalseになると正常に機能します(したがってnoexecはオフのままです)
Jerry

43

GO SQLではありません-これは単に、一部のMS SQLツールで使用されるバッチセパレータです。

これを使用しない場合は、ステートメントが個別に実行されるようにする必要があります-異なるバッチで、またはポピュレーションに動的SQLを使用して(@gbnに感謝):

IF whatever
BEGIN
    ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL;

    EXEC ('UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever')
END

8
はい、わかりました。これは質問の答えにはなりません-同じIFブロックで列を作成して更新する必要があります。
BlueRaja-ダニープフルフフト

@Oded:;ここで助けになりますか?-回答を編集しました:o)
Neil Knight

@ニール-これは私の考えです、はい。
Oded

;も機能しません-パーサーはまだ「無効な列名 'EMP_IS_ADMIN'」
BlueRaja-Danny Pflughoeft、2011年

バッチがコンパイルされるとき、EMP_IS_ADMINは存在しません。stackoverflow.com/questions/4855537/...
GBN

16

以下の例に示すように、sp_executesqlGOステートメント間のコンテンツを個別の文字列に分割して実行することができます。また、例外が発生したデバッグを容易にするために実行されているステートメントを追跡する@statementNo変数もあります。行番号は、エラーの原因となった関連するステートメント番号の先頭を基準にしています。

BEGIN TRAN

DECLARE @statementNo INT
BEGIN TRY
    IF 1=1
    BEGIN
        SET @statementNo = 1
        EXEC sp_executesql
            N'  ALTER TABLE dbo.EMPLOYEE
                    ADD COLUMN EMP_IS_ADMIN BIT NOT NULL'

        SET @statementNo = 2
        EXEC sp_executesql
            N'  UPDATE dbo.EMPLOYEE
                    SET EMP_IS_ADMIN = 1'

        SET @statementNo = 3
        EXEC sp_executesql
            N'  UPDATE dbo.EMPLOYEE
                    SET EMP_IS_ADMIN = 1x'
    END
END TRY
BEGIN CATCH
    PRINT 'Error occurred on line ' + cast(ERROR_LINE() as varchar(10)) 
       + ' of ' + 'statement # ' + cast(@statementNo as varchar(10)) 
       + ': ' + ERROR_MESSAGE()
    -- error occurred, so rollback the transaction
    ROLLBACK
END CATCH
-- if we were successful, we should still have a transaction, so commit it
IF @@TRANCOUNT > 0
    COMMIT

上記の例で示したように、複数行のステートメントを単一引用符(')で囲むだけで簡単に実行することもできます。''スクリプトを生成するときは、文字列内に含まれる一重引用符を二重一重引用符()でエスケープすることを忘れないでください。


これは複数の行に分割されたコマンドでは機能しないと思いませんか?
BlueRaja-ダニープフルフフト

@BlueRaja:例を更新して、それがどのように機能するかを示しました。これらの文字列は、内部に含まれる一重引用符( ')が二重一重引用符(' ')を使用してエスケープされている限り、複数行にすることができます
mellamokb

1
@mellamokb:厳密に言えば、唯一UPDATEニーズsp_executesqlを... stackoverflow.com/questions/4855537/...
GBN

1
@gbn:はい。しかし、これを数百のステートメントに対して自動化する場合は、いつどこで必要かを決めるのではなく、すべてのステートメントに盲目的に適用する方が簡単です。
mellamokb

@gbn @mellamokb:のようなステートメントを意味しましたSELECT * <newline> FROM whatever。独自のEXECステートメントですべての行を実行すると、壊れます。それとも、私はすべてのGO声明で破るように提案していますか?
BlueRaja-Danny Pflughoeft、2011年

9

私は最終的にそれをGOそれ自身の行のすべてのインスタンスを

END
GO

---Automatic replacement of GO keyword, need to recheck IF conditional:
IF whatever
BEGIN

これは、ステートメントのすべてのグループを文字列でラップするよりもはるかに望ましい方法ですが、理想とはほど遠いものです。誰かがより良い解決策を見つけたら、投稿してください。代わりにそれを受け入れます。


6
最初の条件が「この列が存在しない場合」である場合、ブロックの最初のステートメントは「この列を追加する」であり、条件の2番目のチェックは列を見つけ、2番目のステートメントを実行しません
Damien_The_Unbeliever

@Damien:True; 幸いなことに、それは私の場合は決して起こりません(条件は常に特定のテーブルの特定の値のチェックであり、常にIFブロックの最後のステートメントとして追加されます)。SQLでこれを行う良い方法はないようです。
BlueRaja-ダニープフルフフト

Mina Jacobのset noexecAnswerは、ステートメント内のいくつかの呼び出しで他のSSスクリプト(つまり、サブデプロイメントスクリプト)を(コマンドを介して)呼び出すSS モードスクリプト(つまり、マスターデプロイメントスクリプト)で使用するためのこれまでのところ唯一の実用的な答えです。Oded、mellamokb、およびAndyジョイナーのすべてのステートメントをCalls / - で囲むことの回答は、スターターではありません。また、Statement がある場合、- メソッドは機能しません(たとえば、その前に明示が必要です)。SQLCMD:rifexecbeginendbeginendcreatego
トム・

8

文の間のGOではなく、BEGINとENDでステートメントを囲むことができます。

IF COL_LENGTH('Employees','EMP_IS_ADMIN') IS NULL --Column does not exist
BEGIN
    BEGIN
        ALTER TABLE dbo.Employees ADD EMP_IS_ADMIN BIT
    END

    BEGIN
        UPDATE EMPLOYEES SET EMP_IS_ADMIN = 0
    END
END

(Northwindデータベースでテスト済み)

編集:(おそらくSQL2012でテスト済み)


1
-1の理由を教えてください
Andy Joiner 2014

1
なぜ反対票が投じられたのかわからない...私にとっては魅力のように機能します。
ソラリン2015

10
SQL Server 2008 R2を使用すると、これは私には機能しないようですが、「列名が無効です 'EMP_IS_ADMIN'」というエラーが表示されます。UPDATE行。
MerickOWA

BEGIN-ENDバッチ処理は、SQL Server 2016を使用して動作しました。IMOこれは最もクリーンな構文です。
Uber Schnoz 2017年

Mina Jacobのset noexecAnswerは、ステートメント内のいくつかの呼び出しで他のSSスクリプト(つまり、サブデプロイメントスクリプト)を(コマンドを介して)呼び出すSS モードスクリプト(つまり、マスターデプロイメントスクリプト)で使用するためのこれまでのところ唯一の実用的な答えです。Oded、mellamokb、およびAndyジョイナーのすべてのステートメントをCalls / - で囲むことの回答は、スターターではありません。また、Statement がある場合、- メソッドは機能しません(たとえば、その前に明示が必要です)。SQLCMD:rifexecbeginendbeginendcreatego
トム

1

あなたはこの解決策を試すことができます:

if exists(
SELECT...
)
BEGIN
PRINT 'NOT RUN'
RETURN
END

--if upper code not true

ALTER...
GO
UPDATE...
GO

1
複数のif-elseブロックが次々にある場合は、あまり役に立ちません。
ジェリー

0

RAISERROR過去に使ったことがあります

IF NOT whatever BEGIN
    RAISERROR('YOU''RE ALL SET, and sorry for the error!', 20, -1) WITH LOG
END

ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever

-1

GOTOand LABELステートメントを組み込んでコードをスキップし、GOキーワードをそのままにしておくことができます。


5
LABELはSQLに送信されるバッチに含まれていないため、GOステートメント全体で参照できないようです
berkeleybross
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.