How can I exit in the middle of a stored procedure?
I have a stored procedure where I want to bail out early (while trying to debug it). I've tried calling RETURN and RAISERROR, and the sp keeps on running:
CREATE PROCEDURE dbo.Archive_Session #SessionGUID uniqueidentifier AS
print 'before raiserror'
raiserror('this is a raised error', 18, 1)
print 'before return'
return -1
print 'after return'
[snip]
I know it keeps running because I encounter an error further down. I don't see any of my prints. If I comment out the bulk of the stored procedure:
CREATE PROCEDURE dbo.Archive_Session #SessionGUID uniqueidentifier AS
print 'before raiserror'
raiserror('this is a raised error', 18, 1)
print 'before return'
return -1
print 'after return'
/*
[snip]
*/
Then I don't get my error, and I see the results:
before raiserror
Server: Msg 50000, Level 18, State 1, Procedure Archive_Session, Line 5
this is a raised error
before return
So the question is: how do I bail out of a stored procedure in SQL Server?
You can use RETURN to stop execution of a stored procedure immediately. Quote taken from Books Online:
Exits unconditionally from a query or
procedure. RETURN is immediate and
complete and can be used at any point
to exit from a procedure, batch, or
statement block. Statements that
follow RETURN are not executed.
Out of paranoia, I tried yor example and it does output the PRINTs and does stop execution immediately.
Unless you specify a severity of 20 or higher, raiserror will not stop execution. See the MSDN documentation.
The normal workaround is to include a return after every raiserror:
if #whoops = 1
begin
raiserror('Whoops!', 18, 1)
return -1
end
Put it in a TRY/CATCH.
When RAISERROR is run with a severity
of 11 or higher in a TRY block, it
transfers control to the associated
CATCH block
Reference: MSDN.
EDIT: This works for MSSQL 2005+, but I see that you now have clarified that you are working on MSSQL 2000. I'll leave this here for reference.
i figured out why RETURN is not unconditionally returning from the stored procedure. The error i'm seeing is while the stored procedure is being compiled - not when it's being executed.
Consider an imaginary stored procedure:
CREATE PROCEDURE dbo.foo AS
INSERT INTO ExistingTable
EXECUTE LinkedServer.Database.dbo.SomeProcedure
Even though this stord proedure contains an error (maybe it's because the objects have a differnet number of columns, maybe there is a timestamp column in the table, maybe the stored procedure doesn't exist), you can still save it. You can save it because you're referencing a linked server.
But when you actually execute the stored procedure, SQL Server then compiles it, and generates a query plan.
My error is not happening on line 114, it is on line 114. SQL Server cannot compile the stored procedure, that's why it's failing.
And that's why RETURN does not return, because it hasn't even started yet.
This works over here.
ALTER PROCEDURE dbo.Archive_Session
#SessionGUID int
AS
BEGIN
SET NOCOUNT ON
PRINT 'before raiserror'
RAISERROR('this is a raised error', 18, 1)
IF ##Error != 0
RETURN
PRINT 'before return'
RETURN -1
PRINT 'after return'
END
go
EXECUTE dbo.Archive_Session #SessionGUID = 1
Returns
before raiserror
Msg 50000, Level 18, State 1, Procedure Archive_Session, Line 7
this is a raised error
This seems like a lot of code but the best way i've found to do it.
ALTER PROCEDURE Procedure
AS
BEGIN TRY
EXEC AnotherProcedure
END TRY
BEGIN CATCH
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
RETURN --this forces it out
END CATCH
--Stuff here that you do not want to execute if the above failed.
END --end procedure
Its because you have no BEGIN and END statements. You shouldn't be seeing the prints, or errors running this statement, only Statement Completed (or something like that).
Related
There is a Procedure like below
CRAETE PROCEDURE dbo.procname
AS
BEGIN
--BEGIN TRY
SELECT 1/0
--END TRY
--BEGIN CATCH
--END CATCH
END
We are calling this procedure in UNIX SQLCMD as below.
retval=`
DECLARE #val INT
EXEC #val=dbo.procname
SELECT #val`
echo $retval
When we using TRY CATCH block and call the procedure , #val is giving -6 but when we remove TRY CATCH block from procedure, then we are NOT getting any value in #val and it is displaying the SSMS generated Message like
Divide by zero error.
As it is known that, in SQL server, procedures return 0 by default if it runs successfully and Non zero value if it fails.
My requirement is to capture #val int value when it fails as I am using this value to proceed further in UNIX shell script.
So, is there any way that we can capture procedure return value in #val variable if we do not use TRY CATCH block in procedure.
We are working on Migration project and there are number of Procedure in it and we cannot check each and every proc to see if TRY CATCH block is present or not.
I have two procedures, one outer procedure and one inner procedure, where I would like to understand the behaviour of the error handling. The inner procedure provokes an error and is trying to insert something in the catch block into a table. After that the error is raised, passed to the outer procedure and then should roll back the transaction.
I'm trying to understand why my code is throwing the error message:
Msg 50000, Level 11, State 1, Procedure dbo.OuterProcedure, Line 21 [Batch Start Line 9]
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
I would expect the following message:
Msg 50000, Level 11, State 1, Procedure dbo.OuterProcedure, Line 21 [Batch Start Line 9]
Error converting data type varchar to numeric.
I know that the issue comes from the catch block in the inner procedure and it happens because I'm trying to insert something into my log table before raising the error. When I switch those statements or delete the insert, I get the actual error message. I also know that it is not smart to do the logging in the inner procedure and inside a transaction that is rolled back anyways.
I would like to understand what is making this transaction a "doomed" transaction even though the XACT_ABORT is set to off.
Full code:
My main procedure:
CREATE PROCEDURE [dbo].[OuterProcedure]
AS
BEGIN
SET XACT_ABORT OFF;
BEGIN TRY
BEGIN TRANSACTION ;
-- do other stuff
EXEC [dbo].[innerprocedure];
-- do other stuff
COMMIT TRANSACTION ;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
DECLARE #ErrText NVARCHAR(2000);
SET #ErrText = ISNULL(ERROR_MESSAGE(), 'nothing')
RAISERROR(#ErrText, 11, 1) WITH NOWAIT
END CATCH;
END;
My inner procedure:
CREATE PROCEDURE [dbo].[InnerProcedure]
AS
BEGIN
SET XACT_ABORT OFF;
SET NOCOUNT ON;
BEGIN TRY
-- do other stuff
-- provoke error
SELECT
CASE
WHEN 1 = 0
THEN 0.0
ELSE ''
END;
-- do other stuff
END TRY
BEGIN CATCH
DECLARE #ErrText NVARCHAR(2000);
SELECT
#ErrText = ISNULL(ERROR_MESSAGE(), 'nothing');
INSERT INTO [dbo].[logtable]
(
[Message]
, [ErrNr]
)
VALUES
( #ErrText
, -1
);
RAISERROR(#LogText, 11, 0) WITH NOWAIT;
END CATCH;
END;
I would like to understand what is making this transaction a "doomed"
transaction even though the XACT_ABORT is set to off.
XACT_STATE() is -1 in the catch block so the transaction is doomed.
SELECT
CASE
WHEN 1 = 0
THEN 0.0
ELSE ''
END;
Throws error
Error converting data type varchar to numeric.
"Most conversion errors" is one of the error types that Erland Sommarskog puts in the category of errors.
Batch Abortion with Rollback This is the strongest reaction SQL Server
can take to a user error. These are errors that abort execution on the
spot if there is no CATCH handler on the stack and they also roll back
any open transaction. If there is a CATCH handler, the error is
caught, but any open transaction is doomed and must be rolled back.
The behaviour is the same, no matter whether XACT_ABORT is ON or OFF.
The categorisation of error behaviours is somewhat cryptic, undocumented and not intuitive. Read his article for more details.
Let's say I have the following SQL Server stored procedure:
CREATE PROC TestSP AS
select 'results selected' as column1
RAISERROR('this is an error',16,1)
--RETURN 3
If I call it like this,
DECLARE #ReturnCode int
EXEC #ReturnCode = TestSP
select #ReturnCode as ReturnCode
the return code will be -6. If I ALTER PROC to uncomment the --RETURN 3 line in the stored procedure, and again call it, the RETURN 3 will override and cause the return code to be 3.
If I call the procedure from within TRY...CATCH block,
DECLARE #ReturnCode int
BEGIN TRY
EXEC #ReturnCode = TestSP
END TRY
BEGIN CATCH
PRINT 'An error occurred.'
END CATCH
SELECT #ReturnCode as ReturnCode
the return code will be NULL.
Is using a return code (EXEC #ReturnCode = TestSP) not compatible with calling the stored procedure from within a TRY...CATCH block?
The reason the return code is NULL when calling it from a TRY...CATCH block may have something to do with batch abortion.
Microsoft SQL Server gives the programmer the option of EXECuting stored procedures in a way that captures the return code, as you said:
EXEC #ReturnCode = YourStoredProc
but you also have the option of calling your stored procedure from within a TRY...CATCH block, like this
BEGIN TRY
EXEC YourStoredProc
END TRY
BEGIN CATCH
<handle error>
END CATCH
It seems like there's no point in calling your stored procedure with EXEC #ReturnCode = YourStoredProc if you're going to call it from a TRY...CATCH block, because, as you indicated, the return code will be NULL. (Side note: If there is no TRY...CATCH or explicit 'RETURN n' in your code, RAISERROR will set the return code to -1 for an error severity of 11, -2 for 12, -3 for 13, etc. Note, however, that a severity of less than 20 will not stop execution of your code.)
The TRY..CATCH method gives you a lot more options and information to handle errors with. Inside the CATCH block you can use the following system functions (taken from TRY...CATCH documentation):
ERROR_NUMBER() returns the number of the error.
ERROR_SEVERITY() returns the severity.
ERROR_STATE() returns the error state number.
ERROR_PROCEDURE() returns the name of the stored procedure or trigger where the error occurred.
ERROR_LINE() returns the line number inside the routine that caused the error.
ERROR_MESSAGE() returns the complete text of the error message. The text includes the values supplied for any substitutable parameters, such as lengths, object names, or times.
You can test the output of these functions with the following code:
BEGIN TRY
EXEC TestSP --A stored proc fashioned to cause an error
END TRY
BEGIN CATCH
SELECT
'ERROR_NUMBER()', ERROR_NUMBER(),''
UNION SELECT
'ERROR_SEVERITY()',ERROR_SEVERITY(),''
UNION SELECT
'ERROR_STATE() ',ERROR_STATE(),''
UNION SELECT
'ERROR_PROCEDURE()',0,ERROR_PROCEDURE()
UNION SELECT
'ERROR_LINE()',ERROR_LINE(),''
UNION SELECT
'ERROR_MESSAGE()',0,ERROR_MESSAGE()
END CATCH
Finally, it should be noted that when using RAISERROR in a stored procedure called from a TRY...CATCH block, severities <= 10 are not even considered errors, and will not cause a CATCH block in the calling code to fire. Severities 11+ will cause the CATCH block to fire. In most cases, we should use severity level 16 with RAISERROR.
I have the following code inside a cursor in a stored procedure I am working with:
SELECT #err_code = ##error
If #err_code <> 0
BEGIN
ROLLBACK TRAN
Select return_status = 'FAIL',
return_msg = 'Insert Into Errs Warnings Failed !!'
return 16
END
Before this error checking there is some table inserts (still inside the cursor). Recently I encountered an error where the table we were inserting into didn't match the insert statement that was coded (Column name or number of supplied values does not match table definition). I would have thought that this error checking code would have caught this and killed the procedure, but for some reason all it did was print an error message to the log and continue running. Column name or number of supplied values does not match table definition was the error message.
So I'm wondering if this has to do with the return being inside of a cursor. Is is possible that in this scenario all the return 16 would have done was exit the cursor and continue to execute the rest of the stored procedure? Or should it have terminated the procedure entirely? I'm using MS SQL Server 2008.
Thank you!
The issue with this ##ERROR function is , its scope is very limited. It will only be populated if an error occurs in the statement that was executed just before calling ##ERROR function.
If any other statement is executed before or called after that error occurred the ##ERROR will return NULL.
Also to anticipate where error may occur, and capture its value intime and stop the execution there, its a bit of a pain and error prone.
The best way of error handling in sql server would be try..catch block.
BEGIN TRY
BEGIN TRANSACTION
-- your code here
COMMIT TRANSACTION --<-- this will only be executed if nothing
-- goes wrong in prior statements
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
Select return_status = 'FAIL',
return_msg = 'Insert Into Errs Warnings Failed !!'
END CATCH
Using Try...Catch block also gives you access to Error functions which can only be used in CATCH BLOCK and can help you to get detailed information about the error occurred in the Try..Block.
error functions like ERROR_MESSAGE(), ERROR_LINE() , ERROR_STATE(), ERROR_PROCEDURE() etc
I have a stored procedure that seems not to be logging its errors correctly.
The code is erroring, but the catch block doesn't seem to be coming into effect.
The try block is fairly long - but the erroring section is simple and comes rightat the end, so I've precis'd that.
BEGIN TRY
insert into tbl_X
select * from #temp_tbl_Y
RETURN 1
END TRY
BEGIN CATCH
Insert Into ExtractsErrorLog
SELECT
getdate() as ErrorDate
,object_name(##procid) as ProcedureName
,ERROR_NUMBER() as ErrorNumber
,ERROR_LINE() as ErrorLine
,ERROR_MESSAGE() as ErrorMessage
;
DECLARE #errormessage as varchar(max);
DECLARE #errorseverity as int;
DECLARE #errorstate as int;
set #errormessage = ERROR_MESSAGE();
set #errorseverity = ERROR_SEVERITY();
set #errorstate = ERROR_STATE();
RAISERROR (#errormessage,
#errorseverity,
#errorstate
);
END CATCH;
The error the proc is failing on is our old friend
"Column name or number of supplied values does not match table definition."
I've fixed that error - It was a dumb lazy mistake - but I'm baffled why my error logging process didn't seem to be working - no row is being inserted into my ExtractsErrorLog table.
TSQL's TRY...CATCH does not catch that error. This error falls into the "compilation/recompilation" type errors that are not handled by the CATCH block "within the same level of execution".
From MSDN:
The following types of errors are not handled by a CATCH block when
they occur at the same level of execution as the TRY…CATCH construct:
Compile errors, such as syntax errors, that prevent a batch from
running.
Errors that occur during statement-level recompilation, such
as object name resolution errors that occur after compilation because
of deferred name resolution
...
You can use TRY…CATCH to handle errors that occur during compilation
or statement-level recompilation by executing the error-generating
code in a separate batch within the TRY block. For example, you do
this by placing the code in a stored procedure or by executing a
dynamic Transact-SQL statement using sp_executesql. This allows
TRY…CATCH to catch the error at a higher level of execution than the
error occurrence. For example, the following code shows a stored
procedure that generates an object name resolution error. The batch
that contains the TRY…CATCH construct is executing at a higher level
than the stored procedure; and the error, which occurs at a lower
level, is caught.
I ran into similar issues with a script creating a transaction inside a TRY...CATCH that would ROLLBACK the transaction if it failed. A statement inside the transaction was throwing that same error and caused the transaction to never be closed, as the CATCH was never entered.
As mentioned in the MSDN article, one alternative is to create a stored procedure out of your INSERT statement and then call that inside your try/catch. If the sproc is wrong, you'll catch the compilation error while trying to create it. If the table definition later changes to invalidate the sproc, then the TRY...CATCH will catch the exception for you.
If you want it to all live in one script, you can make it a temporary stored procedure, but you will then need to handle the compilation errors while you are creating the sprocs. It's not pretty, but it will work:
-- Creating error sproc to re-use code
CREATE PROCEDURE #HandleError AS
Insert Into ExtractsErrorLog
SELECT GETDATE() as ErrorDate
,object_name(##procid) as ProcedureName
,ERROR_NUMBER() as ErrorNumber
,ERROR_LINE() as ErrorLine
,ERROR_MESSAGE() as ErrorMessage;
DECLARE #errormessage as varchar(max);
DECLARE #errorseverity as int;
DECLARE #errorstate as int;
set #errormessage = ERROR_MESSAGE();
set #errorseverity = ERROR_SEVERITY();
set #errorstate = ERROR_STATE();
RAISERROR ( #errormessage,
#errorseverity,
#errorstate);
GO
-- Create a stored procedure of our INSERT and catch any compilation errors
CREATE PROCEDURE #TEST AS
insert into tbl_X
select * from #temp_tbl_Y
GO
IF (##ERROR <> 0) BEGIN
exec #HandleError
-- If there was an error creating the sprocs, don't continue to the next batch
RETURN
END
-- If compilation succeeded, then run the sproc
BEGIN TRY
exec #TEST
RETURN
END TRY
BEGIN CATCH
exec #HandleError
END CATCH;
I used THROW in my CATCH block before the INSERT statement for my logging - and got the same issue as you. Once I moved THROW after the logging INSERT statement it worked. Looks like THROW might terminate the session.
You don't use THROW in your code example, but thought this might help someone else.
It's your RETURN: "Exits unconditionally from a query or procedure. RETURN is immediate and complete and can be used at any point to exit from a procedure, batch, or statement block."