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のように動作させる方法はありますか?
一般的に言えば、いいえ。 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を混在させるのは良い考えではないと言う学派があります。過去に、これを行うと誤ったロギングと回復不能なデータベースが発生するバグがありました。それにもかかわらず、人々は特に一時テーブルでそれを行います。その結果、コードの追跡が非常に困難になる可能性があります。
これはあなたが探しているものですか?
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;
ポールホワイトの回答に関する「一般的にはノー」の発言に対して、以下は私が質問に直接回答を提供することを願うだけでなく、そのようなプロセスの体系的な制限を示し、簡単な管理や公開に向かない方法からあなたを導きますリスク。
DMLを作成するのと同時にDDLを変更しないようにするために、何度も言及されます。優れたプログラミングは、これらの関数を分離して、サポート性を維持し、スパゲッティ文字列の変更を回避します。
そして、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
[結論]
つまり、SQL Serverの特定のバージョンまたはパッチに対してDDLとDMLを同じバッチで実行できます @ AndriyM-SQL 2017のdbfiddle が指摘していますが、すべてのDMLがサポートされているわけではなく、これは保証されません常にそうなります。動作する場合、それはSQL Serverのバージョンの異常である可能性があり、パッチを適用したり、新しいバージョンに移行したりすると、劇的な問題が発生する可能性があります。
[EXTRA CREDIT]
EXISTSステートメントについては、Paulが述べたように、コードの次のステップに進む前にコードを検証する他の方法がたくさんあります。