Apakah SQL Server mengizinkan (membuat terlihat) DDL di dalam transaksi dengan transaksi sebelum melakukan?

9

Dalam PostgreSQL saya dapat membuat tabel dengan beberapa data uji, dan kemudian dalam suatu transaksi migrasi ke kolom baru dari jenis yang berbeda menghasilkan satu tabel-menulis ulang COMMIT,

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

Diikuti oleh,

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

Namun, hal yang sama di Microsoft SQL Server tampaknya menghasilkan kesalahan. Bandingkan db biola yang berfungsi ini , di mana perintah ADD(kolom) berada di luar transaksi,

-- 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;

untuk biola db ini yang tidak bekerja,

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

Melainkan kesalahan

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

Apakah ada cara untuk membuat transaksi ini terlihat, sehubungan dengan DDL, berperilaku seperti PostgreSQL?

Evan Carroll
sumber

Jawaban:

17

Secara umum, tidak. SQL Server mengkompilasi seluruh kumpulan pada lingkup saat ini sebelum eksekusi sehingga entitas yang direferensikan harus ada (kompilasi tingkat pernyataan juga dapat terjadi kemudian). Pengecualian utama adalah Resolusi Nama Ditangguhkan tetapi itu berlaku untuk tabel, bukan kolom:

Resolusi nama yang ditangguhkan hanya dapat digunakan ketika Anda mereferensikan objek tabel yang tidak ada. Semua objek lain harus ada pada saat prosedur tersimpan dibuat. Misalnya, ketika Anda mereferensikan tabel yang ada dalam prosedur tersimpan, Anda tidak bisa membuat daftar kolom yang tidak ada untuk tabel itu.

Penanganan yang umum melibatkan kode dinamis (seperti pada jawaban Joe ), atau memisahkan DML dan DDL menjadi kelompok-kelompok yang terpisah.

Untuk kasus khusus ini, Anda juga dapat menulis:

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;

Anda masih tidak akan dapat mengakses kolom yang diubah namanya bdi batch dan lingkup yang sama, tetapi itu menyelesaikan pekerjaan.

Berkenaan dengan SQL Server, ada aliran pemikiran yang mengatakan pencampuran DDL dan DML dalam transaksi bukanlah ide bagus. Ada bug di masa lalu di mana melakukan hal ini telah menghasilkan pencatatan yang salah, dan database yang tidak dapat dipulihkan. Meskipun demikian, orang melakukannya, terutama dengan meja sementara. Ini dapat menghasilkan beberapa kode yang cukup sulit untuk diikuti.

Paul White 9
sumber
12

Apakah ini yang Anda cari?

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;
Joe Obbish
sumber
2

Terhadap pernyataan "umumnya tidak ada" pada jawaban Paul White, berikut saya harap menawarkan jawaban langsung untuk pertanyaan tersebut tetapi juga berfungsi untuk menunjukkan keterbatasan sistemik dari proses semacam itu dan menjauhkan Anda dari metode yang tidak memberikan kemudahan dalam pengelolaan dan mengekspos risiko.

Hal dapat disebutkan berkali-kali untuk tidak membuat DDL perubahan saat yang sama Anda membuat DML. Pemrograman yang baik memisahkan fungsi-fungsi ini untuk mempertahankan dukungan dan menghindari perubahan merangkai spageti.

Dan seperti yang ditunjukkan oleh Paul secara ringkas, SQL Server bekerja dalam batch .

Sekarang, Bagi mereka yang meragukan ini berhasil, mungkin tidak pada contoh Anda tetapi beberapa versi seperti 2017 itu benar-benar dapat bekerja! Ini buktinya: masukkan deskripsi gambar di sini

[KODE UJI - MUNGKIN tidak berfungsi pada banyak versi 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

[KESIMPULAN]

Jadi ya Anda dapat melakukan DDL dan DML dalam batch yang sama untuk versi atau tambalan tertentu dari SQL Server seperti yang ditunjukkan oleh @AndriyM - dbfiddle pada SQL 2017 , tetapi tidak semua DML didukung dan tidak ada jaminan hal ini akan selalu menjadi masalah. Jika berhasil, itu mungkin merupakan penyimpangan dari versi SQL Server Anda dan ini dapat menyebabkan masalah dramatis saat Anda menambal atau bermigrasi ke versi baru.

  • Plus, secara umum desain Anda harus mengantisipasi perubahan. Saya memahami kekhawatiran tentang memodifikasi / menambahkan kolom pada tabel, tetapi Anda dapat mendesainnya dengan benar dalam jumlah banyak.

[KREDIT TAMBAHAN]

Adapun pernyataan EXISTS, seperti yang dinyatakan Paul, ada banyak cara lain untuk memvalidasi kode sebelum pindah ke langkah berikutnya dalam kode Anda.

  • Pernyataan EXISTS dapat membantu Anda membuat kode yang berfungsi pada semua versi SQL Server
  • Ini adalah fungsi Boolean yang memungkinkan pemeriksaan kompleks dalam satu pernyataan
clifton_h
sumber
Tidak, Anda tidak bisa memasukkan ke dalam kolom baru jika Anda melakukan ini di batch yang sama di mana Anda membuat kolom. Secara umum, Anda tidak dapat secara statis mereferensikan kolom baru dalam batch yang sama setelah Anda membuatnya. Trik JIKA ADA tidak berfungsi dalam kasus ini. Meminta DML secara dinamis atau melakukannya di batch lain.
Andriy M
@AndriyM maaf, salah membuat pernyataan tentang dbfiddle. Tetapi apakah Anda mencoba ini pada Instance Anda? Ini bekerja pada 2017 SP1. Saya akan mengunggah gif, tetapi apakah Anda menguji ini pada sistem Anda?
clifton_h
i.imgur.com/fhAC7lB.png Anda sebenarnya dapat mengatakan bahwa itu tidak dapat dikompilasi berdasarkan garis bergelombang bdi bawah dalam pernyataan insert. Saya menggunakan SQL Server 2014
Andriy M
@AndriyM menarik. Saya telah melihat ini mempengaruhi pekerjaan sebelumnya, dan tampaknya berfungsi pada beberapa versi SQL Server, seperti SQL Server 2017 yang saya sebutkan.
clifton_h
@AndriyM lihat hasil edit baru untuk dikirim
clifton_h