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


9

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では同じことがエラーを生成するようです。この作業の比較デシベルのフィドル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フィドルに、

-- 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のように動作させる方法はありますか?

回答:


17

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

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

一般的な回避策には、動的コード(Joeの回答のように)、または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を混在させるのは良い考えではないと言う学派があります。過去にこれを行うと、誤ったロギングと回復不能なデータベースが発生するバグがありました。それにもかかわらず、人々は、特に一時テーブルでそれを行います。その結果、コードの追跡が非常に困難になる可能性があります。


12

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

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;

2

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

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

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

さて、これがうまく機能しないのであれば、おそらくインスタンスでは機能しませんが、2017など一部のバージョンでは実際に機能します。ここに証明があります: ここに画像の説明を入力してください

[テストコード-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

[結論]

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

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

[追加クレジット]

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

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

いいえ、を作成しているのと同じバッチでこれを行っている場合、新しい列に挿入することはできません。より一般的には、新しい列を作成した直後は、同じバッチで新しい列を静的に参照することはできません。この場合、IF EXISTSトリックは機能しません。DMLを動的に呼び出すか、別のバッチで実行します。
Andriy M

@AndriyM申し訳ありませんが、dbfiddleに関する記述が誤っています。しかし、インスタンスでこれを試しましたか?2017 SP1で動作します。GIFをアップロードしますが、システムでテストしましたか?
clifton_h

i.imgur.com/fhAC7lB.png実際にbは、挿入ステートメントの下の波線に基づいてコンパイルされないことがわかります。私はSQL Server 2014を使用しています
Andriy M

@AndriyM興味深い。これは以前に動作に影響を及ぼしたことがあり、SQL Server 2017のように、SQL Serverの一部のバージョンで動作するようです。
clifton_h

@AndriyM投稿の新しい編集を参照
clifton_h
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.