web-dev-qa-db-ja.com

SQL Serverは、コミットする前に、トランザクション内のトランザクションへのDDLを許可(可視化)しますか?

PostgreSQLでは、いくつかのテストデータを含むテーブルを作成し、トランザクションで別のタイプの新しい列に移行して、1つのテーブルを作成します- COMMIT

CREATE TABLE foo ( a int );
INSERT INTO foo VALUES (1),(2),(3);

に続く、

BEGIN;
  ALTER TABLE foo ADD COLUMN b varchar;
  UPDATE foo SET b = CAST(a AS varchar);
  ALTER TABLE foo DROP COLUMN a;
COMMIT;

ただし、MicrosoftのSQL Serverでも同じことがエラーを生成するようです。この動作 db fiddle を比較します。ここで、ADD(列)コマンドはトランザクションの外部にあり、

-- txn1
BEGIN TRANSACTION;
  ALTER TABLE foo ADD b varchar;
COMMIT;

-- txn2
BEGIN TRANSACTION;
  UPDATE foo SET b = CAST( a AS varchar );
  ALTER TABLE foo DROP COLUMN a;
COMMIT;

これに db fiddle これは機能しません、

-- txn1
BEGIN TRANSACTION;
  ALTER TABLE foo ADD b varchar;
  UPDATE foo SET b = CAST( a AS varchar );
  ALTER TABLE foo DROP COLUMN a;
COMMIT;

しかし、代わりにエラー

Msg 207 Level 16 State 1 Line 2
Invalid column name 'b'.

とにかく、DDLに関して、このトランザクションを可視化してPostgreSQLのように動作させる方法はありますか?

9
Evan Carroll

一般的に言えば、いいえ。 SQL Serverは、実行前に現在のスコープでbatch全体をコンパイルするため、参照されるエンティティが存在する必要があります(ステートメントレベルの再コンパイルも後で発生する可能性があります)。主な例外は Deferred Name Resolution ですが、列ではなくテーブルに適用されます。

遅延名前解決は、存在しないテーブルオブジェクトを参照する場合にのみ使用できます。他のすべてのオブジェクトは、ストアドプロシージャの作成時に存在している必要があります。たとえば、ストアドプロシージャで既存のテーブルを参照する場合、そのテーブルに存在しない列をリストすることはできません。

一般的な回避策には、動的コード(Joeの answer など)、またはDMLとDDLを別々のバッチに分離することが含まれます。

この特定のケースでは、次のように書くこともできます。

BEGIN TRANSACTION;

    ALTER TABLE dbo.foo
        ALTER COLUMN a varchar(11) NOT NULL
        WITH (ONLINE = ON);

    EXECUTE sys.sp_rename
        @objname = N'dbo.foo.a',
        @newname = N'b',
        @objtype = 'COLUMN';

COMMIT TRANSACTION;

同じバッチとスコープで名前が変更された列bにアクセスすることはできませんが、ジョブは完了します。

SQL Serverに関して、トランザクションにDDLとDMLを混在させるのは良い考えではないと言う学派があります。過去に、これを行うと誤ったロギングと回復不能なデータベースが発生するバグがありました。それにもかかわらず、人々は特に一時テーブルでそれを行います。その結果、コードの追跡が非常に困難になる可能性があります。

17
Paul White 9

これはあなたが探しているものですか?

BEGIN TRANSACTION;
  ALTER TABLE foo ADD b varchar;
  EXEC sp_executesql N'UPDATE foo SET b = CAST( a AS varchar )';
  ALTER TABLE foo DROP COLUMN a;
COMMIT;
12
Joe Obbish

ポールホワイトの回答に関する「一般的にはノー」の発言に対して、以下は私が質問に直接回答を提供することを願うだけでなく、そのようなプロセスの体系的な制限を示し、簡単な管理や公開に向かない方法からあなたを導きますリスク。

DMLを作成するのと同時にDDLを変更しないようにするために、何度も言及されます。優れたプログラミングは、これらの関数を分離して、サポート性を維持し、スパゲッティ文字列の変更を回避します。

そして、Paulが簡潔に指摘したように、SQL Serverはバッチで動作します。

さて、これがうまく機能しないのであれば、おそらくインスタンスでは機能しませんが、2017など一部のバージョンでは実際に機能します。これが証明です: enter image description here

[テストコード-多くのバージョンのSQL Serverでは機能しない可能性があります]

USE master
GO
CREATE TABLE foo (a VARCHAR(11) )
GO
BEGIN TRANSACTION;
    INSERT INTO dbo.foo (a)
    VALUES ('entry')
/*****
[2] Check Values
*****/
    SELECT a FROM dbo.foo
/*****
[3] Add Column
*****/
    ALTER TABLE dbo.foo
        ADD b VARCHAR(11)
/*****
[3] Insert value into this new column in the same batch
-- Again, this is just an example. Please do not do this in production
*****/
    IF EXISTS (SELECT * FROM sys.columns WHERE object_ID('foo') = object_id
            AND name = 'b')
        INSERT INTO dbo.foo (b)
        VALUES ('d')
COMMIT TRANSACTION;
/*****
[4] SELECT outside transaction
-- this will fail
*****/
    --IF EXISTS (SELECT * FROM sys.columns WHERE object_ID('foo') = object_id
    --      AND name = 'b')
    --  SELECT b FROM dbo.foo
-- this will work...but a SELECT * ???
IF EXISTS (SELECT * FROM sys.columns WHERE object_ID('foo') = object_id
            AND name = 'b')
        SELECT * FROM dbo.foo

DROP TABLE dbo.foo

[結論]

つまり、SQL Serverの特定のバージョンまたはパッチに対してDDLとDMLを同じバッチで実行できます @ AndriyM-SQL 2017のdbfiddle が指摘していますが、すべてのDMLがサポートされているわけではなく、これは保証されません常にそうなります。動作する場合、それはSQL Serverのバージョンの異常である可能性があり、パッチを適用したり、新しいバージョンに移行したりすると、劇的な問題が発生する可能性があります。

  • さらに、一般的に、設計は変更を予測する必要があります。列を変更または追加することの懸念がテーブルにある可能性があることを理解していますが、これをバッチで適切に設計できます。

[EXTRA CREDIT]

EXISTSステートメントについては、Paulが述べたように、コードの次のステップに進む前にコードを検証する他の方法がたくさんあります。

  • EXISTSステートメントは、SQL Serverのすべてのバージョンで機能するコードの作成に役立ちます
  • 1つのステートメントで複雑なチェックを可能にするブール関数です。
2
clifton_h