TSQL - Bagaimana cara menggunakan GO di dalam blok BEGIN .. END?

96

Saya membuat skrip untuk memigrasikan perubahan secara otomatis dari beberapa database pengembangan ke pementasan / produksi. Pada dasarnya, ini membutuhkan banyak perubahan-skrip, dan menggabungkannya menjadi satu skrip, membungkus setiap skrip dalam sebuah IF whatever BEGIN ... ENDpernyataan.

Namun, beberapa skrip memerlukan GOpernyataan sehingga, misalnya, parser SQL mengetahui tentang kolom baru setelah dibuat.

ALTER TABLE dbo.EMPLOYEE 
ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO -- Necessary, or next line will generate "Unknown column:  EMP_IS_ADMIN"
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever

Namun, setelah saya membungkusnya dalam satu IFblok:

IF whatever
BEGIN
    ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
    GO
    UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever
END

Gagal karena saya mengirim BEGINtanpa kecocokan END. Namun, jika saya menghapusnya, GOmengeluh lagi tentang kolom yang tidak diketahui.

Apakah ada cara untuk membuat dan memperbarui kolom yang sama dalam satu IFblok?

BlueRaja - Danny Pflughoeft
sumber
2
@gbn: Ya, saya menyadari mengapa ini terjadi (lihat paragraf kedua) ; tetapi saya tidak tahu cara mengatasinya - apakah saya benar - benar perlu mengubah setiap kueri menjadi sekumpulan string !?
BlueRaja - Danny Pflughoeft
@BlueRaja: Apa kekhawatirannya? Jika berhasil, itu yang terpenting pada akhirnya. Jika ada masalah bisnis yang sah dengan solusi yang diberikan, harap jelaskan. Apakah ada sesuatu yang secara khusus membingungkan tentang mengonversi setiap kueri menjadi sekumpulan string?
mellamokb
1
@mellamokb: Ya, ada masalah; jika kata GO digunakan dalam konteks lain (seperti komentar, atau string), skrip tidak akan berfungsi. Selain itu, kami kehilangan nomor baris yang berguna dalam pesan kesalahan jika terjadi kesalahan. Apakah tidak ada cara untuk melakukan ini dengan transaksi? Atau coba / tangkap?
BlueRaja - Danny Pflughoeft
@BlueRaja: 1) Saya yakin GOharus berada dalam satu baris dengan sendirinya, sehingga Anda dapat mencari kasus itu saja, dan tidak setiap kata GO. 2) Anda selalu dapat mencatat pernyataan mana yang berhasil diselesaikan. Atau Anda dapat membungkus semuanya dalam coba / tangkap dan menggunakan nomor baris Anda sendiri menggunakan beberapa variabel, seperti @lineNo, yang Anda lacak, dan laporkan kesalahan. Karena Anda membuat ini secara otomatis, membuat perubahan seperti ini akan sangat mudah. Sepertinya Anda tidak ingin menjelajahi rute ini ketika menurut saya ada solusi yang dapat ditemukan untuk semua masalah Anda.
mellamokb

Jawaban:

45

Saya mengalami masalah yang sama dan akhirnya berhasil menyelesaikannya menggunakan SET NOEXEC .

IF not whatever
BEGIN
    SET NOEXEC ON; 
END

ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever

SET NOEXEC OFF; 
Mina Jacob
sumber
2
Ini adalah solusi yang bagus!
Bazinga
+1! Sejauh ini, ini adalah SATU-SATUNYA Jawaban praktis untuk digunakan dalam SQLCMDSkrip Mode SS (yaitu skrip penerapan utama) yang memanggil (melalui :rperintah) Skrip SS lainnya (yaitu skrip sub-penerapan) dengan beberapa panggilan tersebut di dalam ifPernyataan. Jawaban Oded, mellamokb dan Andy Joiner yang menyertakan semua Pernyataan tersebut di execPanggilan / begin- endadalah bukan permulaan. Selain itu, metode begin- endtidak akan berfungsi jika ada createPernyataan (misalnya, memerlukan eksplisit gosebelumnya). Tapi, man, "Holy double negative, Batman!" ;)
Tom
Untuk keterbacaan (misalnya, untuk membantu mengatasi double-negatif dan membuatnya lebih jelas bahwa itu mensimulasikan virtual if -block), saya akan mengawali blok dengan -- If whateverKomentar, mengindentasi blok dan memposting blok dengan --end If whateverKomentar.
Tom
Anda menyelamatkan daging saya! Saya menjalankan pernyataan merge dan GO bodoh itu tidak suka berada di dalam IF BEGIN END ELSE
Omzig
Hm, saya mendapatkan kesalahan pada pembaruan entah bagaimana setelah set noexec on dijalankan? (galat bahwa nama kolom untuk memperbarui tidak valid) Berjalan di MSSQL 2014 di editor kueri. Berfungsi dengan baik jika kondisi menjadi salah (sehingga noexec tetap mati)
Jerry
43

GO bukan SQL - ini hanyalah pemisah batch yang digunakan di beberapa alat MS SQL.

Jika Anda tidak menggunakannya, Anda perlu memastikan pernyataan dijalankan secara terpisah - baik dalam batch yang berbeda atau dengan menggunakan SQL dinamis untuk populasi (terima kasih @gbn):

IF whatever
BEGIN
    ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL;

    EXEC ('UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever')
END
Oded
sumber
8
Ya aku mengerti itu. Ini tidak menjawab pertanyaan - Saya perlu membuat dan memperbarui kolom di IFblok yang sama .
BlueRaja - Danny Pflughoeft
@Oded: Apakah ada ;bantuan di sini? - Anda baru saja mengedit jawaban Anda: o)
Neil Knight
@ Neil - ini adalah pemikiran saya, ya.
Oded
;juga tidak berfungsi - parser masih memberi saya "Nama kolom tidak valid 'EMP_IS_ADMIN'."
BlueRaja - Danny Pflughoeft
Ketika batch dikompilasi, EMP_IS_ADMIN tidak ada. stackoverflow.com/questions/4855537/…
gbn
16

Anda dapat mencoba sp_executesql, membagi konten di antara setiap GOpernyataan menjadi string terpisah untuk dieksekusi, seperti yang ditunjukkan pada contoh di bawah ini. Selain itu, ada variabel @statementNo untuk melacak pernyataan mana yang sedang dieksekusi untuk memudahkan proses debug di mana pengecualian terjadi. Nomor baris akan relatif terhadap awal nomor pernyataan relevan yang menyebabkan kesalahan.

BEGIN TRAN

DECLARE @statementNo INT
BEGIN TRY
    IF 1=1
    BEGIN
        SET @statementNo = 1
        EXEC sp_executesql
            N'  ALTER TABLE dbo.EMPLOYEE
                    ADD COLUMN EMP_IS_ADMIN BIT NOT NULL'

        SET @statementNo = 2
        EXEC sp_executesql
            N'  UPDATE dbo.EMPLOYEE
                    SET EMP_IS_ADMIN = 1'

        SET @statementNo = 3
        EXEC sp_executesql
            N'  UPDATE dbo.EMPLOYEE
                    SET EMP_IS_ADMIN = 1x'
    END
END TRY
BEGIN CATCH
    PRINT 'Error occurred on line ' + cast(ERROR_LINE() as varchar(10)) 
       + ' of ' + 'statement # ' + cast(@statementNo as varchar(10)) 
       + ': ' + ERROR_MESSAGE()
    -- error occurred, so rollback the transaction
    ROLLBACK
END CATCH
-- if we were successful, we should still have a transaction, so commit it
IF @@TRANCOUNT > 0
    COMMIT

Anda juga dapat dengan mudah menjalankan pernyataan multi-baris, seperti yang ditunjukkan pada contoh di atas, hanya dengan menggabungkannya dalam tanda kutip tunggal ( '). Jangan lupa untuk melepaskan tanda kutip tunggal yang terkandung di dalam string dengan tanda kutip tunggal ganda ( '') saat membuat skrip.

mellamokb.dll
sumber
Jangan berpikir ini akan berhasil untuk perintah yang dibagi menjadi beberapa baris, bukan?
BlueRaja - Danny Pflughoeft
@BlueRaja: Saya telah memperbarui contoh untuk menunjukkan cara kerjanya. String tersebut bisa multi-baris, selama ada tanda kutip tunggal (') di dalamnya yang di-escape dengan menggunakan tanda kutip tunggal ganda (' ')
mellamokb
1
@mellamokb: tegasnya, hanya UPDATE yang membutuhkan sp_executesql ... stackoverflow.com/questions/4855537/…
gbn
1
@gbn: Benar. Tetapi jika Anda akan mengotomatiskan ini untuk 100 pernyataan, akan lebih mudah untuk hanya menerapkannya secara membabi buta di semua pernyataan daripada memutuskan kapan dan di mana Anda membutuhkannya.
mellamokb
@gbn @mellamokb: Yang saya maksud adalah pernyataan seperti SELECT * <newline> FROM whatever. Jika saya mengeksekusi setiap baris dengan pernyataan EXEC-nya sendiri, itu akan rusak. Atau apakah Anda menyarankan saya melanggar setiap GOpernyataan?
BlueRaja - Danny Pflughoeft
9

Saya akhirnya membuatnya bekerja dengan mengganti setiap contoh GOpada barisnya sendiri dengan

END
GO

---Automatic replacement of GO keyword, need to recheck IF conditional:
IF whatever
BEGIN

Ini sangat disukai daripada membungkus setiap kelompok pernyataan dalam sebuah string, tetapi masih jauh dari ideal. Jika ada yang menemukan solusi yang lebih baik, kirimkan dan saya akan menerimanya.

BlueRaja - Danny Pflughoeft
sumber
6
Jika persyaratan pertama adalah "jika kolom ini tidak ada", pernyataan pertama di blok adalah "tambahkan kolom ini", maka pemeriksaan kedua persyaratan akan menemukan kolom dan tidak mengeksekusi pernyataan kedua,
Damien_The_Unbeliever
@Damien: Benar; untungnya, itu tidak akan pernah terjadi dalam kasus saya (kondisional selalu merupakan pemeriksaan untuk nilai tertentu dalam tabel tertentu, yang selalu ditambahkan sebagai pernyataan terakhir dari IFblok). Sepertinya tidak ada cara yang baik untuk melakukan ini di SQL.
BlueRaja - Danny Pflughoeft
set noexecJawaban Mina Jacob adalah SATU-SATUNYA Jawaban praktis sejauh ini untuk digunakan dalam SQLCMDSkrip Mode SS (yaitu skrip penerapan utama) yang memanggil (melalui :rperintah) Skrip SS lainnya (yaitu skrip sub-penerapan) dengan beberapa panggilan tersebut di dalam ifPernyataan. Jawaban Oded, mellamokb, dan Andy Joiner yang menyertakan semua Pernyataan tersebut di execPanggilan / begin- endadalah bukan permulaan. Selain itu, metode begin- endtidak akan berfungsi jika ada createPernyataan (misalnya, memerlukan eksplisit gosebelumnya).
Tom
8

Anda dapat menyertakan pernyataan di BEGIN dan END sebagai ganti peralihan GO

IF COL_LENGTH('Employees','EMP_IS_ADMIN') IS NULL --Column does not exist
BEGIN
    BEGIN
        ALTER TABLE dbo.Employees ADD EMP_IS_ADMIN BIT
    END

    BEGIN
        UPDATE EMPLOYEES SET EMP_IS_ADMIN = 0
    END
END

(Diuji pada database Northwind)

Sunting: (Mungkin diuji pada SQL2012)

Andy Joiner
sumber
1
Tolong beri alasan untuk -1
Andy Joiner
1
Tidak tahu mengapa itu tidak disukai ... bekerja seperti pesona bagi saya.
Thorarin
10
Menggunakan SQL Server 2008 R2, ini sepertinya tidak berhasil untuk saya, saya masih mendapatkan kesalahan 'Nama kolom tidak valid' EMP_IS_ADMIN '.' di baris UPDATE.
MerickOWA
Batch BEGIN-END bekerja untuk saya menggunakan SQL Server 2016. IMO ini adalah sintaks terbersih.
Uber Schnoz
set noexecJawaban Mina Jacob adalah SATU-SATUNYA Jawaban praktis sejauh ini untuk digunakan dalam SQLCMDSkrip Mode SS (yaitu skrip penerapan utama) yang memanggil (melalui :rperintah) Skrip SS lainnya (yaitu skrip sub-penerapan) dengan beberapa panggilan tersebut di dalam ifPernyataan. Jawaban Oded, mellamokb, dan Andy Joiner yang menyertakan semua Pernyataan tersebut di execPanggilan / begin- endadalah bukan permulaan. Selain itu, metode begin- endtidak akan berfungsi jika ada createPernyataan (misalnya, memerlukan eksplisit gosebelumnya).
Tom
1

Anda dapat mencoba solusi ini:

if exists(
SELECT...
)
BEGIN
PRINT 'NOT RUN'
RETURN
END

--if upper code not true

ALTER...
GO
UPDATE...
GO
Luk
sumber
1
Tidak terlalu berguna jika Anda memiliki beberapa blok if-else satu demi satu, bukan?
Jerry
0

Saya telah menggunakan RAISERRORdi masa lalu untuk ini

IF NOT whatever BEGIN
    RAISERROR('YOU''RE ALL SET, and sorry for the error!', 20, -1) WITH LOG
END

ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever
kavun
sumber
-1

Anda dapat memasukkan pernyataan GOTOdan LABELuntuk melewati kode, sehingga membiarkan GOkata kunci tetap utuh.

jim a
sumber
5
Tampaknya LABEL tidak dapat direferensikan di seluruh pernyataan GO karena tidak dalam batch yang dikirim ke SQL
berkeleybross