MS SQLサーバーで実行されるスクリプトを作成しています。このスクリプトは複数のステートメントを実行するため、トランザクションの必要があります。ステートメントの1つが失敗すると、全体の実行が停止され、変更がロールバックされます。
ALTER TABLEステートメントを発行してテーブルに列を追加し、新しく追加された列を更新すると、このトランザクションモデルの作成で問題が発生します。新しく追加された列にすぐにアクセスするには、GOコマンドを使用してALTER TABLEステートメントを実行し、UPDATEステートメントを呼び出します。私が直面している問題は、IFステートメント内でGOコマンドを発行できないことです。 IFステートメントは、トランザクションモデル内で重要です。これは、実行しようとしているスクリプトのサンプルコードです。また、GOコマンドを発行すると、@ errorCode変数が破棄され、使用する前にコードで宣言する必要があることに注意してください(これは以下のコードにはありません)。
BEGIN TRANSACTION
DECLARE @errorCode INT
SET @errorCode = @@ERROR
-- **********************************
-- * Settings
-- **********************************
IF @errorCode = 0
BEGIN
BEGIN TRY
ALTER TABLE Color ADD [CodeID] [uniqueidentifier] NOT NULL DEFAULT ('{00000000-0000-0000-0000-000000000000}')
GO
END TRY
BEGIN CATCH
SET @errorCode = @@ERROR
END CATCH
END
IF @errorCode = 0
BEGIN
BEGIN TRY
UPDATE Color
SET CodeID= 'B6D266DC-B305-4153-A7AB-9109962255FC'
WHERE [Name] = 'Red'
END TRY
BEGIN CATCH
SET @errorCode = @@ERROR
END CATCH
END
-- **********************************
-- * Check @errorCode to issue a COMMIT or a ROLLBACK
-- **********************************
IF @errorCode = 0
BEGIN
COMMIT
PRINT 'Success'
END
ELSE
BEGIN
ROLLBACK
PRINT 'Failure'
END
したがって、私が知りたいのは、この問題を回避し、ALTER TABLEステートメントを発行して列を追加し、その列を更新することです。これらはすべて、トランザクションユニットとして実行されます。
GOはT-SQLコマンドではありません。バッチ区切り文字です。クライアントツール(SSM、sqlcmd、osqlなど)はそれを使用して、各GOでファイルを効果的にcutし、個々のバッチをサーバーに送信します。したがって、明らかに、IF内でGOを使用することはできません。また、変数がバッチにまたがってスコープに及ぶことを期待することもできません。
また、 XACT_STATE()
をチェックせずに例外をキャッチして、トランザクションが終了しないことを確認することはできません。
IDにGUIDを使用することは、常に少なくとも疑わしいものです。
NOT NULL制約を使用し、'{00000000-0000-0000-0000-000000000000}'
のようなデフォルトの 'guid'を提供することも正しくありません。
更新しました:
XACT_ABORT
を使用して、エラーが強制的にバッチを中断するようにします。これは、メンテナンススクリプト(スキーマの変更)で頻繁に使用されます。ストアドプロシージャとアプリケーションロジックスクリプトは一般にTRY-CATCHブロックを代わりに使用しますが、適切な注意を払って: 例外処理とネストされたトランザクション 。スクリプト例:
:on error exit
set xact_abort on;
go
begin transaction;
go
if columnproperty(object_id('Code'), 'ColorId', 'AllowsNull') is null
begin
alter table Code add ColorId uniqueidentifier null;
end
go
update Code
set ColorId = '...'
where ...
go
commit;
go
成功したスクリプトのみがCOMMIT
に到達します。エラーがあると、スクリプトは中止され、ロールバックされます。
COLUMNPROPERTY
を使用して列の存在を確認しましたが、代わりに任意の方法を使用できます(lookup sys.columns
)。
Remusのコメントに直交して、できることはsp_executesqlで更新を実行することです。
ALTER TABLE [Table] ADD [Xyz] NVARCHAR(256);
DECLARE @sql NVARCHAR(2048) = 'UPDATE [Table] SET [Xyz] = ''abcd'';';
EXEC sys.sp_executesql @query = @sql;
アップグレードスクリプトを作成するときにこれを行う必要がありました。通常はGOを使用しますが、条件付きで処理する必要があります。
Remusにはほぼ同意しますが、SET XACT_ABORT ONおよびXACT_STATEでこれを行うことができます
基本的に
Red Gate SQL Compareなどのツールはこの手法を使用します
何かのようなもの:
SET XACT_ABORT ON
GO
BEGIN TRANSACTION
GO
IF COLUMNPROPERTY(OBJECT_ID('Color'), 'CodeID', ColumnId) IS NULL
ALTER TABLE Color ADD CodeID [uniqueidentifier] NULL
GO
IF XACT_STATE() = 1
UPDATE Color
SET CodeID= 'B6D266DC-B305-4153-A7AB-9109962255FC'
WHERE [Name] = 'Red'
GO
IF XACT_STATE() = 1
COMMIT TRAN
--else would be rolled back
デフォルトも削除しました。 GUID値の場合、値なし= NULL。これは一意であることを意図しています。涙で終わるため、すべての行をすべてゼロに設定しないでください...
GOなしで試してみましたか?
通常、同じスクリプト内でテーブルの変更とデータの変更を混在させないでください。
コードを個別のバッチに分割したくない場合、別の代替方法は、EXECを使用してネストされたスコープ/バッチを作成することです as as here