SQL Server - dừng hoặc ngắt thực thi tập lệnh SQL


325

Có cách nào để dừng ngay việc thực thi tập lệnh SQL trong máy chủ SQL, như lệnh "break" hoặc "exit" không?

Tôi có một tập lệnh thực hiện một số xác nhận và tra cứu trước khi nó bắt đầu thực hiện chèn và tôi muốn nó dừng lại nếu bất kỳ xác thực hoặc tra cứu nào thất bại.

Câu trả lời:


370

Các RAISERROR phương pháp

raiserror('Oh no a fatal error', 20, -1) with log

Điều này sẽ chấm dứt kết nối, do đó ngăn phần còn lại của tập lệnh chạy.

Lưu ý rằng cả mức độ nghiêm trọng 20 hoặc cao hơn và WITH LOGtùy chọn là cần thiết để nó hoạt động theo cách này.

Điều này thậm chí hoạt động với các câu lệnh GO, ví dụ.

print 'hi'
go
raiserror('Oh no a fatal error', 20, -1) with log
go
print 'ho'

Sẽ cung cấp cho bạn đầu ra:

hi
Msg 2745, Level 16, State 2, Line 1
Process ID 51 has raised user error 50000, severity 20. SQL Server is terminating this process.
Msg 50000, Level 20, State 1, Line 1
Oh no a fatal error
Msg 0, Level 20, State 0, Line 0
A severe error occurred on the current command.  The results, if any, should be discarded.

Lưu ý rằng 'ho' không được in.

CÁCH MẠNG:

  • Điều này chỉ hoạt động nếu bạn đã đăng nhập với tư cách quản trị viên (vai trò 'sysadmin') và cũng khiến bạn không có kết nối cơ sở dữ liệu.
  • Nếu bạn KHÔNG đăng nhập với tư cách quản trị viên, bản thân cuộc gọi RAISEERROR () sẽ thất bại và tập lệnh sẽ tiếp tục thực thi .
  • Khi được gọi với sqlcmd.exe, mã thoát 2745 sẽ được báo cáo.

Tham khảo: http://www.mydatabaseupport.com/forums/ms-sqlserver/174037-sql-server-2000-abort-whole-script.html#post761334

Phương pháp noexec

Một phương pháp khác hoạt động với các câu lệnh GO là set noexec on. Điều này khiến phần còn lại của tập lệnh bị bỏ qua. Nó không chấm dứt kết nối, nhưng bạn cần noexectắt lại trước khi bất kỳ lệnh nào được thực thi.

Thí dụ:

print 'hi'
go

print 'Fatal error, script will not continue!'
set noexec on

print 'ho'
go

-- last line of the script
set noexec off -- Turn execution back on; only needed in SSMS, so as to be able 
               -- to run this script again in the same session.

14
Thật tuyệt vời! Đó là một chút của một cách tiếp cận "cây gậy lớn", nhưng có những lúc bạn thực sự cần nó. Lưu ý rằng nó yêu cầu cả hai mức độ nghiêm trọng 20 (hoặc cao hơn) và "VỚI LOG".
Rob Garrison

5
Lưu ý rằng với phương thức noexec, phần còn lại của tập lệnh vẫn được diễn giải, do đó bạn vẫn sẽ gặp các lỗi thời gian biên dịch, chẳng hạn như cột không tồn tại. Nếu bạn muốn xử lý một cách có điều kiện các thay đổi lược đồ đã biết liên quan đến các cột bị thiếu bằng cách bỏ qua một số mã, cách duy nhất tôi biết là sử dụng: r trong chế độ sqlcommand để tham chiếu các tệp bên ngoài.
David Eison

20
Điều noexec là tuyệt vời. Cảm ơn rất nhiều!
Gaspa79

2
"Điều này sẽ chấm dứt kết nối" - có vẻ như nó không, ít nhất đó là những gì tôi đang thấy.
jcollum

6
Tôi đã thử phương pháp này và không nhận được kết quả đúng khi tôi nhận ra ... Chỉ có một E trong raiserror ...
bobkingof12vs

187

Chỉ cần sử dụng RETURN (nó sẽ hoạt động cả bên trong và bên ngoài một thủ tục được lưu trữ).


2
Vì một số lý do, tôi đã nghĩ rằng sự trở lại không hoạt động trong các kịch bản, nhưng tôi chỉ thử nó, và nó đã làm được! Cảm ơn
Andy White

4
Trong tập lệnh, bạn không thể thực hiện TRẢ LẠI với giá trị như bạn có thể làm trong một thủ tục được lưu trữ, nhưng bạn có thể thực hiện TRẢ LẠI.
Rob Garrison

53
Không, nó chỉ chấm dứt cho đến GO tiếp theo Lô tiếp theo (sau GO) sẽ chạy như bình thường
mortb

2
nguy hiểm khi giả định vì nó sẽ tiếp tục sau đó GO tiếp theo.
Justin

1
GO là một bộ kết thúc tập lệnh hoặc dấu phân cách; Đó không phải là mã SQL. GO chỉ là một hướng dẫn cho máy khách bạn đang sử dụng để gửi lệnh đến công cụ cơ sở dữ liệu mà tập lệnh mới đang bắt đầu sau dấu phân cách GO.
Kỹ sư đảo ngược

50

Nếu bạn có thể sử dụng chế độ SQLCMD, thì câu thần chú

:on error exit

(BAO GỒM dấu hai chấm) sẽ khiến RAISERROR thực sự dừng tập lệnh. Ví dụ,

:on error exit

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[SOMETABLE]') AND type in (N'U')) 
    RaisError ('This is not a Valid Instance Database', 15, 10)
GO

print 'Keep Working'

sẽ xuất ra:

Msg 50000, Level 15, State 10, Line 3
This is not a Valid Instance Database
** An error was encountered during execution of batch. Exiting.

và đợt sẽ dừng lại. Nếu chế độ SQLCMD không được bật, bạn sẽ gặp lỗi phân tích về dấu hai chấm. Thật đáng tiếc, nó không hoàn toàn chống đạn như thể tập lệnh được chạy mà không ở chế độ SQLCMD, SQL Management Studio sẽ vượt qua ngay cả lỗi thời gian phân tích! Tuy nhiên, nếu bạn đang chạy chúng từ dòng lệnh, điều này là tốt.


4
Nhận xét tuyệt vời, cảm ơn. Tôi sẽ thêm rằng trong chế độ SSMS SQLCmd được chuyển đổi trong menu Truy vấn.
David Peters

Điều này rất hữu ích - có nghĩa là bạn không cần tùy chọn -b khi chạy
JonnyRaa

2
sau đó là câu thần chú ... nhưng làm thế nào để tôi sử dụng Magic Missle?!
JJS

1
hoàn hảo. không yêu cầu quyền người dùng cực kỳ sysadmin
Pac0

21

Tôi sẽ không sử dụng RAISERROR- SQL có các câu lệnh IF có thể được sử dụng cho mục đích này. Thực hiện xác nhận và tra cứu của bạn và đặt các biến cục bộ, sau đó sử dụng giá trị của các biến trong các câu lệnh IF để chèn các điều kiện.

Bạn sẽ không cần phải kiểm tra kết quả khác nhau của mọi bài kiểm tra xác nhận. Bạn thường có thể làm điều này chỉ với một biến cờ để xác nhận tất cả các điều kiện được thông qua:

declare @valid bit

set @valid = 1

if -- Condition(s)
begin
  print 'Condition(s) failed.'
  set @valid = 0
end

-- Additional validation with similar structure

-- Final check that validation passed
if @valid = 1
begin
  print 'Validation succeeded.'

  -- Do work
end

Ngay cả khi xác thực của bạn phức tạp hơn, bạn chỉ cần một vài biến cờ để đưa vào (các) kiểm tra cuối cùng của bạn.


Vâng, tôi đang sử dụng IF trong các phần khác của tập lệnh, nhưng tôi không muốn phải kiểm tra mọi biến cục bộ trước khi tôi thử thực hiện thao tác chèn. Tôi chỉ muốn dừng toàn bộ tập lệnh và buộc người dùng kiểm tra các đầu vào. (Đây chỉ là một kịch bản nhanh và bẩn)
Andy White

4
Tôi không chắc tại sao câu trả lời này đã được đánh dấu bởi vì nó đúng về mặt kỹ thuật, chỉ là những gì người đăng "muốn" làm.
John Sansom

Có thể có nhiều khối trong Begin..End không? Ý nghĩa TUYÊN BỐ; ĐI; TUYÊN BỐ; ĐI; Vân vân? Tôi đang gặp lỗi và tôi đoán đó có thể là lý do.
Nenotlep

3
Điều này đáng tin cậy hơn nhiều RAISERROR, đặc biệt là nếu bạn không biết ai sẽ chạy tập lệnh và với những đặc quyền nào.
Cypher

@ John Sansom: Vấn đề duy nhất tôi thấy ở đây là câu lệnh IF không hoạt động nếu bạn đang cố gắng phân nhánh qua câu lệnh GO. Đây là một vấn đề lớn nếu tập lệnh của bạn dựa vào câu lệnh GO (ví dụ: câu lệnh DDL). Dưới đây là một ví dụ hoạt động mà không có tuyên bố đi đầu tiên:declare @i int = 0; if @i=0 begin select '1st stmt in IF block' go end else begin select 'ELSE here' end go
James Jensen

16

Trong SQL 2012+, bạn có thể sử dụng NHIỀU .

THROW 51000, 'Stopping execution because validation failed.', 0;
PRINT 'Still Executing'; -- This doesn't execute with THROW

Từ MSDN:

Tăng một ngoại lệ và chuyển thực thi sang khối CATCH của cấu trúc TRY CAT CATCH ... Nếu cấu trúc TRY CAT CATCH không khả dụng, phiên kết thúc. Số dòng và thủ tục đặt ngoại lệ được đặt. Mức độ nghiêm trọng được đặt thành 16.


1
NHIỀU có nghĩa là để thay thế RAISERROR, nhưng bạn không thể ngăn các lô tiếp theo trong cùng một tệp tập lệnh với nó.
NRzingh

Đúng @NRadyh. Đó là nơi câu trả lời của Blorgbeard thực sự là giải pháp duy nhất. Mặc dù vậy, nó yêu cầu sysadmin (mức độ nghiêm trọng 20) và khá nặng tay nếu không có nhiều lô trong tập lệnh.
Jordan Parker

2
đặt xact hủy bỏ nếu bạn muốn hủy bỏ quá trình chuyển đổi hiện tại.
Nurettin

13

Tôi đã mở rộng giải pháp bật / tắt noexec thành công với một giao dịch để chạy tập lệnh một cách toàn diện hoặc không có gì.

set noexec off

begin transaction
go

<First batch, do something here>
go
if @@error != 0 set noexec on;

<Second batch, do something here>
go
if @@error != 0 set noexec on;

<... etc>

declare @finished bit;
set @finished = 1;

SET noexec off;

IF @finished = 1
BEGIN
    PRINT 'Committing changes'
    COMMIT TRANSACTION
END
ELSE
BEGIN
    PRINT 'Errors occured. Rolling back changes'
    ROLLBACK TRANSACTION
END

Rõ ràng trình biên dịch "hiểu" biến @finished trong IF, ngay cả khi có lỗi và việc thực thi bị vô hiệu hóa. Tuy nhiên, giá trị được đặt thành 1 chỉ khi thực thi không bị tắt. Do đó tôi có thể cam kết hoặc hoàn trả giao dịch một cách độc đáo.


Tôi không hiểu. Tôi làm theo hướng dẫn. Tôi đã nhập SQL sau mỗi GO. IF (XACT_STATE()) <> 1 BEGIN Set NOCOUNT OFF ;THROW 525600, 'Rolling back transaction.', 1 ROLLBACK TRANSACTION; set noexec on END; Nhưng việc thực thi không bao giờ dừng lại và tôi đã kết thúc với ba lỗi "Quay lại giao dịch" được nêu ra. Có ý kiến ​​gì không?
dùng1161391

12

bạn có thể gói câu lệnh SQL của mình trong một vòng lặp WHILE và sử dụng BREAK nếu cần

WHILE 1 = 1
BEGIN
   -- Do work here
   -- If you need to stop execution then use a BREAK


    BREAK; --Make sure to have this break at the end to prevent infinite loop
END

5
Tôi giống như vẻ ngoài của nó, nó có vẻ đẹp hơn một chút so với lỗi tăng. Chắc chắn không muốn quên giờ nghỉ cuối cùng!
Andy White

1
Bạn cũng có thể sử dụng một biến và ngay lập tức đặt nó ở đầu vòng lặp để tránh "chia". DECLARE @ST INT; SET @ST = 1; WHILE @ST = 1; BEGIN; SET @ST = 0; ...; ENDNhiều dài dòng hơn, nhưng quái gì, dù sao đó cũng là TSQL ;-)

Đây là cách một số người thực hiện goto, nhưng nó khó hiểu hơn so với goto.
Nurettin

Cách tiếp cận này bảo vệ khỏi một GO không thường xuyên. Đánh giá cao.
it3xl

10

Bạn có thể thay đổi luồng thực thi bằng các câu lệnh GOTO :

IF @ValidationResult = 0
BEGIN
    PRINT 'Validation fault.'
    GOTO EndScript
END

/* our code */

EndScript:

2
sử dụng goto là một cách chấp nhận được để xử lý ngoại lệ. Giảm số lượng biến và lồng nhau và không gây ra ngắt kết nối. Có lẽ tốt hơn là xử lý ngoại lệ cổ xưa mà kịch bản SQL Server cho phép.
Antonio Drusin

Giống như TẤT CẢ các đề xuất khác ở đây, điều này không hoạt động nếu "mã của chúng tôi" có chứa câu lệnh "GO".
Mike Gledhill

9

Hơn nữa phương pháp refinig Sglasses, các đường trên buộc việc sử dụng các chế độ SQLCMD, và một trong hai treminates các scirpt nếu không sử dụng chế độ SQLCMD hoặc sử dụng :on error exitđể thoát trên bất kỳ lỗi
context_info được sử dụng để theo dõi các tiểu bang.

SET CONTEXT_INFO  0x1 --Just to make sure everything's ok
GO 
--treminate the script on any error. (Requires SQLCMD mode)
:on error exit 
--If not in SQLCMD mode the above line will generate an error, so the next line won't hit
SET CONTEXT_INFO 0x2
GO
--make sure to use SQLCMD mode ( :on error needs that)
IF CONTEXT_INFO()<>0x2 
BEGIN
    SELECT CONTEXT_INFO()
    SELECT 'This script must be run in SQLCMD mode! (To enable it go to (Management Studio) Query->SQLCMD mode)\nPlease abort the script!'
    RAISERROR('This script must be run in SQLCMD mode! (To enable it go to (Management Studio) Query->SQLCMD mode)\nPlease abort the script!',16,1) WITH NOWAIT 
    WAITFOR DELAY '02:00'; --wait for the user to read the message, and terminate the script manually
END
GO

----------------------------------------------------------------------------------
----THE ACTUAL SCRIPT BEGINS HERE-------------

2
Đây là cách duy nhất tôi tìm thấy để giải quyết vấn đề mất trí SSMS là không thể hủy bỏ tập lệnh. Nhưng tôi đã thêm 'SET NOEXEC OFF' ngay từ đầu và 'SET NOEXEC ON' nếu không ở chế độ SQLCMD, nếu không thì tập lệnh thực tế sẽ tiếp tục trừ khi bạn đưa ra lỗi ở cấp 20 với nhật ký.
Mark Sowul

8

Đây có phải là một thủ tục lưu trữ? Nếu vậy, tôi nghĩ bạn chỉ có thể thực hiện Trả về, chẳng hạn như "Trả về NULL";


Cảm ơn câu trả lời, thật tốt khi biết, nhưng trong trường hợp này, đó không phải là một Proc được lưu trữ, chỉ là một tập tin kịch bản
Andy White

1
@Gordon Không phải lúc nào (ở đây tôi đang tìm kiếm). Xem các câu trả lời khác (GO đi lên, vì một điều)
Mark Sowul

6

Tôi sẽ đề nghị bạn bọc khối mã thích hợp của mình trong khối thử bắt. Sau đó, bạn có thể sử dụng sự kiện Raiserror với mức độ nghiêm trọng là 11 để phá vỡ khối bắt nếu bạn muốn. Nếu bạn chỉ muốn đột kích nhưng tiếp tục thực thi trong khối thử thì sử dụng mức độ nghiêm trọng thấp hơn.

Có lý?

Chúc mừng, John

[Đã chỉnh sửa để bao gồm BOL Reference]

http://msdn.microsoft.com/en-us/l Library / ms175976 (MySQL.90) .aspx


Tôi chưa bao giờ thấy một thử bắt trong SQL - bạn có phiền khi đăng một ví dụ nhanh về ý của bạn không?
Andy White

2
nó mới đến năm 2005. BEGIN TRY {sql_statement | statement_block} END TRY BEGIN CATCH {sql_statement | statement_block} KẾT THÚC [; ]
Sam

@Andy: Đã thêm tham chiếu, ví dụ bao gồm.
John Sansom

2
Khối TRY-CATCH không cho phép GO bên trong chính nó.
AntonK

4

bạn có thể sử dụng RAISERROR .


3
Điều này vô nghĩa đối với tôi - đưa ra một lỗi có thể tránh được (giả sử chúng ta đang nói về xác nhận tham chiếu ở đây) là một cách khủng khiếp để làm điều này nếu việc xác thực có thể xảy ra trước khi việc chèn diễn ra.
Dave Swersky

2
raiserror có thể được sử dụng như một tin nhắn thông tin với cài đặt mức độ nghiêm trọng thấp.
Mladen Prajdic 18/03/2016

2
Kịch bản sẽ tiếp tục trừ khi một số điều kiện được nêu trong câu trả lời được chấp nhận được đáp ứng.
Eric J.

4

Không ai trong số này hoạt động với các tuyên bố 'GO'. Trong mã này, bất kể mức độ nghiêm trọng là 10 hay 11, bạn sẽ nhận được câu lệnh IN cuối cùng.

Tập lệnh kiểm tra:

-- =================================
PRINT 'Start Test 1 - RAISERROR'

IF 1 = 1 BEGIN
    RAISERROR('Error 1, level 11', 11, 1)
    RETURN
END

IF 1 = 1 BEGIN
    RAISERROR('Error 2, level 11', 11, 1)
    RETURN
END
GO

PRINT 'Test 1 - After GO'
GO

-- =================================
PRINT 'Start Test 2 - Try/Catch'

BEGIN TRY
    SELECT (1 / 0) AS CauseError
END TRY
BEGIN CATCH
    SELECT ERROR_MESSAGE() AS ErrorMessage
    RAISERROR('Error in TRY, level 11', 11, 1)
    RETURN
END CATCH
GO

PRINT 'Test 2 - After GO'
GO

Các kết quả:

Start Test 1 - RAISERROR
Msg 50000, Level 11, State 1, Line 5
Error 1, level 11
Test 1 - After GO
Start Test 2 - Try/Catch
 CauseError
-----------

ErrorMessage
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Divide by zero error encountered.

Msg 50000, Level 11, State 1, Line 10
Error in TRY, level 11
Test 2 - After GO

Cách duy nhất để thực hiện công việc này là viết kịch bản mà không có GOcâu lệnh. Đôi khi điều đó thật dễ dàng. Đôi khi nó khá khó khăn. (Sử dụng một cái gì đó như IF @error <> 0 BEGIN ....)


Không thể làm điều đó với THỦ TỤC TẠO vv. Xem câu trả lời của tôi để có giải pháp.
Blorgbeard đã hết

Giải pháp của Blogbeard là tuyệt vời. Tôi đã làm việc với SQL Server trong nhiều năm và đây là lần đầu tiên tôi thấy điều này.
Rob Garrison

4

Tôi sử dụng RETURNở đây mọi lúc, làm việc theo kịch bản hoặcStored Procedure

Hãy chắc chắn rằng bạn ROLLBACKgiao dịch nếu bạn đang ở trong một, nếu không RETURNngay lập tức sẽ dẫn đến một giao dịch không được cam kết mở


5
Không hoạt động với tập lệnh chứa nhiều lô (câu lệnh GO) - xem câu trả lời của tôi để biết cách thực hiện.
Blorgbeard đã hết

1
TRẢ LẠI chỉ thoát khỏi khối báo cáo hiện tại. Nếu bạn đang ở trong khối IF END, việc thực thi sẽ tiếp tục sau END. Điều này có nghĩa là bạn không thể sử dụng RETURN để kết thúc thực hiện sau khi kiểm tra một số điều kiện, bởi vì bạn sẽ luôn ở trong khối IF END.
cdonner

3

Đây là giải pháp của tôi:

...

BEGIN
    raiserror('Invalid database', 15, 10)
    rollback transaction
    return
END

3

Bạn có thể sử dụng câu lệnh GOTO. Thử cái này. Đây là sử dụng đầy đủ cho bạn.

WHILE(@N <= @Count)
BEGIN
    GOTO FinalStateMent;
END

FinalStatement:
     Select @CoumnName from TableName

GOTO được coi là một hoạt động mã hóa tồi, nên sử dụng "TRY..CATCH", vì nó đã được giới thiệu kể từ SQL Server 2008, tiếp theo là TÊN vào năm 2012.
Eddie Kumar

1

Thx cho câu trả lời!

raiserror()hoạt động tốt nhưng bạn không nên quên returncâu lệnh nếu không đoạn script tiếp tục không có lỗi! (làm căng người đột kích không phải là "throwerror" ;-)) và tất nhiên là thực hiện quay lại nếu cần thiết!

raiserror() thật tuyệt khi nói với người thực hiện kịch bản rằng đã xảy ra sự cố.


1

Nếu bạn chỉ đang thực thi một tập lệnh trong Management Studio và muốn dừng thực thi hoặc giao dịch rollback (nếu được sử dụng) với lỗi đầu tiên, thì cách tốt nhất tôi nghĩ là sử dụng thử khối bắt (SQL 2005 trở đi). Điều này hoạt động tốt trong Studio quản lý nếu bạn đang thực hiện một tập tin kịch bản. Proc lưu trữ luôn có thể sử dụng này là tốt.


1
Câu trả lời của bạn thêm gì vào câu trả lời được chấp nhận với hơn 60 lượt upvote? Bạn đọc nó xong chưa? Kiểm tra câu hỏi metaSO này và Jon Skeet: Blog mã hóa về cách đưa ra câu trả lời đúng.
Yaroslav

0

Ngày trước, chúng tôi đã sử dụng những thứ sau đây ... hoạt động tốt nhất:

RAISERROR ('Error! Connection dead', 20, 127) WITH LOG

0

Gửi nó trong một khối thử bắt, sau đó thực thi sẽ được chuyển để bắt.

BEGIN TRY
    PRINT 'This will be printed'
    RAISERROR ('Custom Exception', 16, 1);
    PRINT 'This will not be printed'
END TRY
BEGIN CATCH
    PRINT 'This will be printed 2nd'
END CATCH;
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.