web-dev-qa-db-ja.com

テーブルに列を追加し、トランザクション内で更新します

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ステートメントを発行して列を追加し、その列を更新することです。これらはすべて、トランザクションユニットとして実行されます。

64
Guillermo Gomez

GOはT-SQLコマンドではありません。バッチ区切り文字です。クライアントツール(SSM、sqlcmd、osqlなど)はそれを使用して、各GOでファイルを効果的にcutし、個々のバッチをサーバーに送信します。したがって、明らかに、IF内でGOを使用することはできません。また、変数がバッチにまたがってスコープに及ぶことを期待することもできません。

また、 XACT_STATE() をチェックせずに例外をキャッチして、トランザクションが終了しないことを確認することはできません。

IDにGUIDを使用することは、常に少なくとも疑わしいものです。

NOT NULL制約を使用し、'{00000000-0000-0000-0000-000000000000}'のようなデフォルトの 'guid'を提供することも正しくありません。

更新しました:

  • ALTERとUPDATEを2つのバッチに分けます。
  • エラー時にスクリプトを中断するには、sqlcmd拡張機能を使用します。これは sqlcmdモードがオンの場合のSSMS 、sqlcmdでサポートされており、クライアントライブラリでも簡単にサポートできます: dbutilsqlcmd
  • 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 )。

42
Remus Rusanu

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を使用しますが、条件付きで処理する必要があります。

21
Mark Sowul

Remusにはほぼ同意しますが、SET XACT_ABORT ONおよびXACT_STATEでこれを行うことができます

基本的に

  • SET XACT_ABORT ONは、エラーおよびROLLBACKで各バッチを中止します
  • 各バッチはGOで区切られています
  • エラー時に次のバッチに実行がジャンプします
  • 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。これは一意であることを意図しています。涙で終わるため、すべての行をすべてゼロに設定しないでください...

18
gbn

GOなしで試してみましたか?

通常、同じスクリプト内でテーブルの変更とデータの変更を混在させないでください。

2
HLGEM

コードを個別のバッチに分割したくない場合、別の代替方法は、EXECを使用してネストされたスコープ/バッチを作成することです as as here

1
M.Sabaa