TSQL - Cách sử dụng GO bên trong khối BEGIN .. END?


96

Tôi đang tạo một tập lệnh để tự động di chuyển các thay đổi từ nhiều cơ sở dữ liệu phát triển sang dàn dựng / sản xuất. Về cơ bản, cần một loạt các kịch bản thay đổi và kết hợp chúng thành một tập lệnh duy nhất, gói mỗi tập lệnh trong một IF whatever BEGIN ... ENDcâu lệnh.

Tuy nhiên, một số tập lệnh yêu cầu một GOcâu lệnh để, chẳng hạn, trình phân tích cú pháp SQL biết về một cột mới sau khi nó được tạo.

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

Tuy nhiên, khi tôi bọc nó trong một IFkhối:

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

Nó không thành công vì tôi đang gửi một BEGINmà không có kết quả phù hợp END. Tuy nhiên, nếu tôi xóa GOnó, nó lại phàn nàn về một cột không xác định.

Có cách nào để tạo và cập nhật cùng một cột trong một IFkhối duy nhất không?



2
@gbn: Vâng, tôi nhận ra tại sao điều này xảy ra (xem đoạn thứ hai) ; nhưng tôi không biết phải làm thế nào để giải quyết vấn đề đó - tôi có thực sự cần biến mọi truy vấn thành một chuỗi không !?
BlueRaja - Danny Pflughoeft

@BlueRaja: Mối quan tâm là gì? Nếu nó hoạt động, đó là tất cả những gì quan trọng vào cuối ngày. Nếu có vấn đề kinh doanh hợp pháp với giải pháp được cung cấp, vui lòng thể hiện điều đó. Có điều gì đó gây khó chịu cụ thể về việc chuyển đổi mọi truy vấn thành một loạt các chuỗi không?
mellamokb

1
@mellamokb: Vâng, có một vấn đề; nếu từ GO được sử dụng trong bất kỳ ngữ cảnh nào khác (chẳng hạn như nhận xét hoặc chuỗi), tập lệnh sẽ không hoạt động. Ngoài ra, chúng tôi mất các số dòng hữu ích trong thông báo lỗi trong trường hợp có bất kỳ sự cố nào. Không có cách nào để làm điều này với các giao dịch? Hay thử / bắt?
BlueRaja - Danny Pflughoeft

@BlueRaja: 1) Tôi tin rằng GObản thân nó phải ở trên một dòng, vì vậy bạn chỉ có thể tìm kiếm trường hợp đó, và không phải mọi trường hợp của từ GO. 2) Bạn luôn có thể ghi lại những câu lệnh nào đã được hoàn thành thành công. Hoặc bạn có thể kết thúc toàn bộ vấn đề trong một thử / bắt và sử dụng số dòng của riêng bạn bằng cách sử dụng một số biến, như @lineNo, mà bạn theo dõi và báo cáo lỗi. Vì bạn đang tạo các tệp này tự động, nên việc thực hiện các thay đổi như thế này sẽ rất dễ dàng. Có vẻ như bạn hoàn toàn không muốn khám phá tuyến đường này khi tôi nghĩ rằng có những giải pháp được tìm thấy cho tất cả các mối quan tâm của bạn.
mellamokb

Câu trả lời:


44

Tôi đã gặp vấn đề tương tự và cuối cùng đã giải quyết được nó bằng 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; 

2
Đây là một giải pháp tuyệt vời!
Bazinga

+1! Đây là Câu trả lời thực tế DUY NHẤT cho đến nay để sử dụng trong SQLCMDTập lệnh chế độ SS (tức là tập lệnh triển khai chính) gọi (thông qua :rlệnh) Tập lệnh SS khác (tức là tập lệnh triển khai phụ) với một số lệnh gọi đó bên trong ifCâu lệnh. Câu trả lời của Oded, mellamokb và Andy Joiner về việc bao gồm tất cả các Tuyên bố đó trong execCuộc gọi / begin- endcủa là không bắt đầu. Ngoài ra, phương thức begin- endsẽ không hoạt động nếu có một createTuyên bố (ví dụ: yêu cầu rõ ràng gotrước nó). Nhưng, anh bạn, "Âm bản kép thần thánh, Người dơi!" ;)
Tom

Để dễ đọc (ví dụ: để giúp khắc phục các phủ định kép và làm rõ ràng hơn rằng nó đang mô phỏng một -block ảo if ), tôi sẽ đặt tiền tố khối bằng một -- If whateverNhận xét, thụt lề khối và đăng lên khối bằng một --end If whateverNhận xét.
Tom

Bạn đã cứu thịt xông khói của tôi! Tôi đã chạy báo cáo hợp nhất và những của GO câm không thích được bên trong một NẾU BEGIN END ELSE
Omzig

Hm, tôi gặp lỗi trên bản cập nhật bằng cách nào đó sau khi thiết lập noexec trên đã được thực thi? (lỗi tên cột để cập nhật không hợp lệ) Đang chạy trên MSSQL 2014 trong trình chỉnh sửa truy vấn. Hoạt động tốt nếu điều kiện chuyển thành false (do đó noexec vẫn tắt)
Jerry

43

GO không phải là SQL - nó chỉ đơn giản là một dấu phân tách hàng loạt được sử dụng trong một số công cụ MS SQL.

Nếu bạn không sử dụng điều đó, bạn cần đảm bảo các câu lệnh được thực thi riêng biệt - trong các lô khác nhau hoặc bằng cách sử dụng SQL động cho tập hợp (cảm ơn @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

8
Vâng, tôi hiểu điều đó. Điều này không trả lời câu hỏi - Tôi cần tạo và cập nhật một cột trong cùng một IFkhối.
BlueRaja - Danny Pflughoeft

@Oded: Có ;giúp đỡ ở đây không? - Bạn vừa chỉnh sửa câu trả lời của mình: o)
Neil Knight

@Neil - đây là suy nghĩ của tôi, vâng.
Oded

;cũng không hoạt động - trình phân tích cú pháp vẫn cho tôi "Tên cột không hợp lệ 'EMP_IS_ADMIN'."
BlueRaja - Danny Pflughoeft

Khi lô được biên dịch, EMP_IS_ADMIN không tồn tại. stackoverflow.com/questions/4855537/…
gbn

16

Bạn có thể thử sp_executesqltách nội dung giữa mỗi GOcâu lệnh thành một chuỗi riêng biệt để thực thi, như được minh họa trong ví dụ bên dưới. Ngoài ra, có một biến @statementNo để theo dõi câu lệnh nào đang được thực thi để dễ dàng gỡ lỗi khi xảy ra ngoại lệ. Số dòng sẽ liên quan đến phần đầu của số báo cáo có liên quan đã gây ra lỗi.

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

Bạn cũng có thể dễ dàng thực thi các câu lệnh nhiều dòng, như được minh họa trong ví dụ trên, bằng cách chỉ cần đặt chúng trong dấu ngoặc kép ( '). Đừng quên thoát khỏi bất kỳ dấu nháy đơn nào chứa bên trong chuỗi bằng dấu nháy kép ( '') khi tạo tập lệnh.


Đừng nghĩ rằng điều này sẽ hoạt động đối với các lệnh được chia thành nhiều dòng, phải không?
BlueRaja - Danny Pflughoeft

@BlueRaja: Tôi đã cập nhật ví dụ để hiển thị cách nó hoạt động. Những chuỗi có thể được đa dòng, miễn là bất kỳ dấu nháy đơn ( ') chứa bên trong đang trốn thoát bằng cách sử dụng một dấu nháy kép-đơn (' ')
mellamokb

1
@mellamokb: nói đúng ra, chỉ CẬP NHẬT mới cần sp_executesql ... stackoverflow.com/questions/4855537/…
gbn

1
@gbn: Đúng. Nhưng nếu bạn định tự động hóa điều này cho 100 câu lệnh, sẽ dễ dàng hơn nếu bạn chỉ áp dụng nó một cách mù quáng trên tất cả các câu lệnh thay vì quyết định khi nào và ở đâu bạn cần nó.
mellamokb

@gbn @mellamokb: Ý tôi là những câu như SELECT * <newline> FROM whatever. Nếu tôi thực hiện mọi dòng với câu lệnh EXEC của riêng nó, điều đó sẽ bị hỏng. Hay bạn đang đề nghị tôi phá vỡ mọi GOtuyên bố?
BlueRaja - Danny Pflughoeft

9

Cuối cùng, tôi đã làm cho nó hoạt động bằng cách thay thế mọi phiên bản GOtrên dòng riêng của nó bằng

END
GO

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

Điều này rất thích hợp để gói mọi nhóm câu lệnh trong một chuỗi, nhưng vẫn còn xa lý tưởng. Nếu ai tìm thấy giải pháp tốt hơn, hãy đăng nó và tôi sẽ chấp nhận nó.


6
Nếu điều kiện đầu tiên là "nếu cột này không tồn tại", câu lệnh đầu tiên trong khối là "thêm cột này", thì lần kiểm tra thứ hai của điều kiện sẽ tìm thấy cột và không thực hiện câu lệnh thứ hai,
Damien_The_Un Believer

@Damien: Đúng; may mắn thay, điều đó sẽ không bao giờ xảy ra trong trường hợp của tôi (điều kiện luôn là kiểm tra cho một giá trị cụ thể trong một bảng cụ thể, luôn được thêm vào dưới dạng câu lệnh cuối cùng của IFkhối). Có vẻ như không có cách nào tốt để làm điều này trong SQL.
BlueRaja - Danny Pflughoeft

set noexecCâu trả lời của Mina Jacob là Câu trả lời thực tế DUY NHẤT cho đến nay để sử dụng trong SQLCMDTập lệnh chế độ SS (tức là tập lệnh triển khai chính) gọi (thông qua :rlệnh) Tập lệnh SS khác (tức là tập lệnh triển khai phụ) với một số lệnh gọi đó bên trong ifCâu lệnh. Câu trả lời của Oded, mellamokb và Andy Joiner về việc bao gồm tất cả các Tuyên bố đó trong execCuộc gọi / begin- endcủa là không bắt đầu. Ngoài ra, phương thức begin- endsẽ không hoạt động nếu có một createTuyên bố (ví dụ: yêu cầu rõ ràng gotrước nó).
Tom

8

Bạn có thể đặt các câu lệnh bằng BEGIN và END thay vì GO ở giữa

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

(Đã thử nghiệm trên cơ sở dữ liệu Northwind)

Chỉnh sửa: (Có thể được thử nghiệm trên SQL2012)


1
Xin vui lòng cho lý do -1
Andy Joiner

1
Không biết tại sao nó bị từ chối ... hoạt động như một sự quyến rũ đối với tôi.
Thorarin

10
Sử dụng SQL Server 2008 R2, điều này dường như không hiệu quả với tôi, tôi vẫn gặp lỗi 'Tên cột không hợp lệ' EMP_IS_ADMIN '.' trên dòng CẬP NHẬT.
MerickOWA

Lô BEGIN-END phù hợp với tôi khi sử dụng SQL Server 2016. IMO đây là cú pháp rõ ràng nhất.
Uber Schnoz

set noexecCâu trả lời của Mina Jacob là Câu trả lời thực tế DUY NHẤT cho đến nay để sử dụng trong SQLCMDTập lệnh chế độ SS (tức là tập lệnh triển khai chính) gọi (thông qua :rlệnh) Tập lệnh SS khác (tức là tập lệnh triển khai phụ) với một số lệnh gọi đó bên trong ifCâu lệnh. Câu trả lời của Oded, mellamokb và Andy Joiner về việc bao gồm tất cả các Tuyên bố đó trong execCuộc gọi / begin- endcủa là không bắt đầu. Ngoài ra, phương thức begin- endsẽ không hoạt động nếu có một createTuyên bố (ví dụ: yêu cầu rõ ràng gotrước nó).
Tom

1

Bạn có thể thử giải pháp này:

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

--if upper code not true

ALTER...
GO
UPDATE...
GO

1
Không hữu ích lắm nếu bạn có một số khối if-else nối tiếp nhau, phải không?
Jerry

0

Tôi đã sử dụng RAISERRORtrong quá khứ cho điều này

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

-1

Bạn có thể kết hợp câu lệnh a GOTOLABELđể bỏ qua mã, do đó giữ GOnguyên các từ khóa.


5
Dường như nhãn không thể được tham chiếu qua báo cáo GO vì chúng không nằm trong hàng loạt gửi đến SQL
berkeleybross
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.