Comparing two stored procedures with transactions - sql-server

I want to know if both stored procedures below could perform well to do the same thing or if there is any major difference? I have tested them and both are working, but I don't know which one I should use.
I want to select just one of the procedures, if both would perform just fine, that's alright, I want to understand what major different could arise if they are indeed different.
I also want to ensure that the transaction will be rolled back if there is an error in the procedure.
Thanks!
CREATE PROCEDURE spDeleteAnInactiveEmployee
#TrainerID int,
#EActive char (1)
AS
BEGIN
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN
IF (SELECT COUNT(*) FROM EmployeeDetails ed
WHERE TrainerID = #TrainerID) = 0
RAISERROR ('Trainer details were not deleted. Trainer ID does not exist.', 16, 1)
IF EXISTS (SELECT * FROM EmployeeDetails ed
WHERE TrainerID = #TrainerID AND EActive = 'Y')
RAISERROR ('Trainer details were not deleted. Trainer is still active.', 16, 1)
DELETE FROM [EmployeeDetails]
WHERE TrainerID = #TrainerID AND EActive = 'N'
COMMIT TRAN
PRINT 'Employee ID' + CAST (#TrainerID AS VARCHAR) + ' was successfully deleted.'
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK;
THROW;
END CATCH;
END
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROC spDeleteAnInactiveEmployee
#TrainerID int,
#EActive char (1)
AS
BEGIN TRY
BEGIN TRAN
IF (SELECT COUNT(*) FROM EmployeeDetails ed
WHERE TrainerID = #TrainerID) = 0
RAISERROR ('Trainer details were not deleted. Trainer ID does not exist.', 16, 1)
IF EXISTS (SELECT * FROM EmployeeDetails ed
WHERE TrainerID = #TrainerID AND EActive = 'Y')
RAISERROR ('Trainer details were not deleted. Trainer is still active.', 16, 1)
DELETE FROM [EmployeeDetails]
WHERE TrainerID = #TrainerID AND EActive = 'N'
COMMIT TRAN
BEGIN
PRINT 'Employee ID' + CAST (#TrainerID AS VARCHAR) + ' was successfully deleted.'
END
END TRY
BEGIN CATCH
ROLLBACK
PRINT 'An error has occurred and the transaction was not committed'
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_STATE() AS ErrorState,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

Related

Transactions and ##Error function in SQL Server

I just want to check if the place where I put the ##Error and Begin/Commit tran is correct?
I am unsure if I should you the Begin Tran over the DELETE statement instead? And does the ##ERROR make any sense at all?
Thanks!
CREATE PROCEDURE spDeleteAnInactiveEmployee
#TrainerID int,
#EActive char (1)
AS
BEGIN TRY
BEGIN TRAN
IF (SELECT COUNT(*) FROM EmployeeDetails ed
WHERE TrainerID = #TrainerID) = 0
RAISERROR ('Trainer details were not deleted. Trainer ID does not exist.', 16, 1)
IF EXISTS (SELECT * FROM EmployeeDetails ed
WHERE TrainerID = #TrainerID AND EActive = 'Y')
RAISERROR ('Trainer details were not deleted. Trainer is still active.', 16, 1)
DELETE FROM [EmployeeDetails]
WHERE TrainerID = #TrainerID AND EActive = 'N'
IF ##ERROR = 0
COMMIT TRAN
BEGIN
PRINT 'Employee ID' + CAST (#TrainerID AS VARCHAR) + ' was successfully deleted.'
END
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_STATE() AS ErrorState,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage;
IF (XACT_STATE()) = -1
BEGIN
PRINT 'Transaction was not committed'
ROLLBACK TRANSACTION;
END;
IF (XACT_STATE()) = 1
BEGIN
PRINT 'Transaction was committed'
COMMIT TRANSACTION;
END;
END CATCH;
GO
##ERROR is unnecessary when you use TRY/CATCH. Before TRY/CATCH you had to check ##ERROR after each statement that might fail and use GOTO to force control flow to an error label.
So this should be something like:
CREATE PROCEDURE spDeleteAnInactiveEmployee
#TrainerID int,
#EActive char (1)
AS
BEGIN
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN
IF (SELECT COUNT(*) FROM EmployeeDetails ed
WHERE TrainerID = #TrainerID) = 0
RAISERROR ('Trainer details were not deleted. Trainer ID does not exist.', 16, 1)
IF EXISTS (SELECT * FROM EmployeeDetails ed
WHERE TrainerID = #TrainerID AND EActive = 'Y')
RAISERROR ('Trainer details were not deleted. Trainer is still active.', 16, 1)
DELETE FROM [EmployeeDetails]
WHERE TrainerID = #TrainerID AND EActive = 'N'
COMMIT TRAN
PRINT 'Employee ID' + CAST (#TrainerID AS VARCHAR) + ' was successfully deleted.'
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK;
THROW;
END CATCH;
END

How to rollback transaction in a stored procedure?

When the internal SP tries to rollback transaction it completed with an error:
Msg 266, Level 16, State 2, Procedure ptest, Line 0 [Batch Start Line
37] Transaction count after EXECUTE indicates a mismatching number of
BEGIN and COMMIT statements. Previous count = 1, current count = 0.
Is it possible to rollback transaction inside the internal SP?
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ptest]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql #statement = N'CREATE PROCEDURE [dbo].[ptest] AS'
END
GRANT EXECUTE on [dbo].[ptest] to public;
GO
ALTER PROCEDURE [dbo].[ptest]
#parrollback bit = 0
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT OFF
select ##TRANCOUNT as '##TRANCOUNT:[ptest] '
if #parrollback is not null and #parrollback>0
if ##TRANCOUNT>0 rollback tran;
END
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[pcaller]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql #statement = N'CREATE PROCEDURE [dbo].[pcaller] AS'
END
GRANT EXECUTE on [dbo].[pcaller] to public;
GO
ALTER PROCEDURE [dbo].[pcaller]
AS
BEGIN
SET NOCOUNT ON
begin tran
select ##TRANCOUNT as '##TRANCOUNT: before [ptest]'
exec ptest 1
select ##TRANCOUNT as '##TRANCOUNT: after [ptest] '
if ##TRANCOUNT>0 rollback tran;
END
GO
-------------
exec pcaller
/*
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ptest]') AND type in (N'P', N'PC'))
drop proc pcaller
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ptest]') AND type in (N'P', N'PC'))
drop proc ptest
*/
try not to handle parent transactions inside a child procedure (exception when XACT_STATE() = -1). Handle the transaction at the "execution" level that started it.
if a procedure is executed in a parent transaction, then create a savepoint and rollback to it when needed. capture the execution result of the child procedure and handle the transaction at the parent level (if the parent is the one that begun the transaction).
CREATE OR ALTER PROCEDURE [dbo].[ptest] #parrollback bit = 0
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT OFF
DECLARE #trancount INT = ##TRANCOUNT;
IF #trancount = 0
BEGIN
BEGIN TRANSACTION;
END
ELSE
BEGIN
SAVE TRANSACTION MySavepoint;
END
--do stuff.........
--when it is time to commit or check for errors
--assume #parrollback is the main control criterium
IF #parrollback = 1
BEGIN
IF #trancount = 0
BEGIN
ROLLBACK TRANSACTION;
RETURN(0);
END
ELSE
BEGIN
ROLLBACK TRANSACTION MySavePoint
RETURN (1);
END
END
--just handle #parrollback <> 1, for completeness of the test
IF #trancount = 0
BEGIN
COMMIT TRANSACTION;
END
RETURN (0);
END
GO
CREATE OR ALTER PROCEDURE dbo.pcaller
AS
BEGIN
SET NOCOUNT ON
DECLARE #ptestexec INT;
BEGIN TRANSACTION
select ##TRANCOUNT as '##TRANCOUNT: before [ptest]'
EXEC #ptestexec = dbo.ptest #parrollback = 1;
IF #ptestexec = 1
BEGIN
ROLLBACK TRANSACTION
END
ELSE
BEGIN
COMMIT TRANSACTION
END
--execute ptest, outside of a transaction
EXEC #ptestexec = dbo.ptest #parrollback = 0;
SELECT ##TRANCOUNT AS trancount1;
EXEC #ptestexec = dbo.ptest #parrollback = 1;
SELECT ##TRANCOUNT AS trancount2;
--execute ptest, outside of a transaction
BEGIN TRANSACTION;
--ptest executed in a parent transaction
EXEC #ptestexec = dbo.ptest #parrollback = 0;
SELECT ##TRANCOUNT AS trancount3; --ptest does not affect the parent transactions
COMMIT TRANSACTION --or rollback
END
GO
EXEC dbo.pcaller
GO

Try-Catch with Stored Procedures in SQL server

I have created a stored procedure with try-catch for deleting records.A message should be displayed if a record is deleted or if there is an error.The delete procedure works but the message is not being displayed.
Also,it asks me to input a value for #msg and #return when executing the procedure.
Here is my Stored Procedure code:
IF OBJECT_ID('[dbo].[usp_AttendanceDelete]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[usp_AttendanceDelete]
END
GO
CREATE PROCEDURE [dbo].[usp_AttendanceDelete]
#A_ID int,
#msg VARCHAR(50) OUTPUT,
#return INT OUTPUT
AS
BEGIN
SET NOCOUNT ON
BEGIN TRANSACTION
BEGIN TRY
DELETE
FROM [dbo].[Attendance]
WHERE [A_ID] = #A_ID
SET #msg = 'Attendance Deleted'
SET #return = 1
END TRY
BEGIN CATCH
--SELECT error_message() as error
SET #msg = 'Attendance Delete FAIL.'
SET #return = 0
GOTO fail_rollback
END CATCH
COMMIT TRANSACTION
RETURN
fail_rollback:
ROLLBACK TRANSACTION
RETURN
END
GO
Here is the code to execute the procedure:
EXECUTE [dbo].[usp_AttendanceDelete]
#A_ID ='234',
#msg='success',
#return ='1'
You can supply variables rather than values when calling the stored procedure and you need to remember to mark them as output also:
DECLARE #msg varchar(50)
DECLARE #return int
EXECUTE [dbo].[usp_AttendanceDelete]
#A_ID ='234',
#msg=#msg output,
#return=#return output
--Do something with msg/return
Try this Query below
IF OBJECT_ID('[dbo].[usp_AttendanceDelete]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[usp_AttendanceDelete]
END
GO
CREATE PROCEDURE [dbo].[usp_AttendanceDelete]
#A_ID int,
#msg VARCHAR(100) OUTPUT,
#return INT OUTPUT
AS
BEGIN TRANSACTION;
BEGIN TRY
IF EXISTS(SELECT 1 FROM [Attendance] WHERE [A_ID] = #A_ID)
Begin
DELETE
FROM [dbo].[Attendance]
WHERE [A_ID] = #A_ID
SELECT #msg = 'Attendance Deleted'
SELECT #return = 1
END
ELSE
Begin
SELECT #msg='IN Put Record doesn''t exists'
END
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_SEVERITY() AS ErrorSeverity
,ERROR_STATE() AS ErrorState
,ERROR_PROCEDURE() AS ErrorProcedure
,ERROR_LINE() AS ErrorLine
,ERROR_MESSAGE() AS ErrorMessage;
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH;
IF ##TRANCOUNT > 0
COMMIT TRANSACTION;
GO
DECLARE #msg VARCHAR(100),#return INt
EXEC DBO.[usp_AttendanceDelete] 2,#msg OUT,#return OUT
SELECT #msg, #return

Delete Non-existing records From Table in a Stored Procedure with Try Catch shows completed successfully

Ok, I'm literally getting bald from pulling my hair now! Almost 10 hours spending on this but couldn't figure out. I'm trying write a T-SQL stored procedure to delete records from 2 tables that match the input data, and print a message if delete was successful and commit the transaction, and if no records found, rollback the transaction and handle errors using a Try Catch. I have written the code below, but the problem is that even if the data don't match the input, I still get the "Command(s) completed successfully." message! I appreciate your help to fix my mistake. So far my code looks like this:
Use Northwind
Go
Create Procedure uspDeleteOrder
#orderID int As
Set nocount on
If Exists (Select * from Orders Where OrderID = #orderID)
Begin Try
Begin Transaction
Delete From dbo.[Order Details]
Where dbo.[Order Details].OrderID = #orderID
Delete From Orders
Where OrderID = #orderID
COMMIT TRANSACTION
Print 'The article has been deleted!'
End Try
Begin Catch
IF (##trancount > 0)
Begin
ROLLBACK TRANSACTION
End
PRINT '*************Error Detail****************'
PRINT 'Error Number :' + CAST(ERROR_NUMBER() AS VARCHAR)
PRINT 'Error Severity:' + CAST(ERROR_SEVERITY() AS VARCHAR)
PRINT 'Error State :' + CAST(ERROR_STATE() AS VARCHAR)
PRINT 'Error Line :' + CAST(ERROR_LINE() AS VARCHAR)
PRINT 'Error Message :' + ERROR_MESSAGE()
End Catch
Exec uspDeleteOrder 11077
UPDATE: Ok, I edited my code to this, I guess pretty much does the job, however I'm not sure if it's the correct way of doing it:
Create Procedure uspDeleteOrder
#orderID int As
Set nocount on
If Exists (Select * from Orders Where OrderID = #orderID)
Begin
Begin Try
Begin Transaction
Delete From dbo.[Order Details]
Where dbo.[Order Details].OrderID = #orderID
Delete From Orders
Where OrderID = #orderID
COMMIT TRANSACTION
Print 'The Order has been deleted!'
return
End Try
Begin Catch
IF (##trancount > 0)
Begin
ROLLBACK TRANSACTION
RAISERROR ('Error', 16,1);
End
PRINT '*************Error Detail****************'
PRINT 'Error Number :' + CAST(ERROR_NUMBER() AS VARCHAR)
PRINT 'Error Severity:' + CAST(ERROR_SEVERITY() AS VARCHAR)
PRINT 'Error State :' + CAST(ERROR_STATE() AS VARCHAR)
PRINT 'Error Line :' + CAST(ERROR_LINE() AS VARCHAR)
PRINT 'Error Message :' + ERROR_MESSAGE()
End Catch
End
Else
Begin
RAISERROR ('No Order ID', 16,1);
End
Any opinions?
So if there where no orderdetails deleted you want to print this ?
You can do that like this example below.
It is easy to modify to throw an exception in stead of printing or whatever you require
Also why the return statement ?
Create Procedure uspDeleteOrder
#orderID int As
Set nocount on
declare #DeleteOrderDetailCount int
If Exists (Select * from Orders Where OrderID = #orderID)
Begin
Begin Try
Begin Transaction
Delete From dbo.[Order Details]
Where dbo.[Order Details].OrderID = #orderID
select #DeleteOrderDetailCount = ##ROWCOUNT
Delete From Orders
Where OrderID = #orderID
COMMIT TRANSACTION
if #DeleteOrderDetailCount = 0
begin
Print 'There where no orders details to delete'
end
Print 'The Order has been deleted!'
-- why a return here ????
-- return
End Try
Begin Catch
IF (##trancount > 0)
Begin
ROLLBACK TRANSACTION
RAISERROR ('Error', 16,1);
End
PRINT '*************Error Detail****************'
PRINT 'Error Number :' + CAST(ERROR_NUMBER() AS VARCHAR)
PRINT 'Error Severity:' + CAST(ERROR_SEVERITY() AS VARCHAR)
PRINT 'Error State :' + CAST(ERROR_STATE() AS VARCHAR)
PRINT 'Error Line :' + CAST(ERROR_LINE() AS VARCHAR)
PRINT 'Error Message :' + ERROR_MESSAGE()
End Catch
End
Else
Begin
RAISERROR ('No Order ID', 16,1);
End
Have you tried capturing rows affected with ##RowCount
DECLARE #v1 INT
DELETE From dbo.[Order Details]
Where dbo.[Order Details].OrderID = #orderID
SET #v1 = ##ROWCOUNT
It would allow you to implement some logic to capture unwanted scenarios.
You need to RAISERROR from within the Catch Block.
At the moment you are catching the error and selecting out the error details but the stored procedure still returns success.
Add a Line
RAISERROR ('Err', 16,1);
to the catch block

ERROR_PROCEDURE() returning NULL for code executed by SP_EXECUTESQL

I have a stored procedure with a TRY CATCH statement and within that TRY CATCH I am calling another stored procedure which is throwing an error. An exception is thrown and caught however if the error is within the called stored procedure this is not shown in the ERROR_PROCEDURE() it is set to NULL. It seems the reason is due to Dynamic SQL being executed in the called stored procedure.
ALTER PROC dbo.MyError AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN
--do stuff here
--SQL CODE
SELECT 'HELLO' AS hello
--then call sproc
EXEC dbo.MyInnerError
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRANSACTION
END
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH
END
GO
ALTER PROC dbo.MyInnerError AS
BEGIN
DECLARE #SQl nvarchar(50) = 'SELECT 1/0 as DYNAMIC_FAIL';
EXEC SP_EXECUTESQL #SQl;
END
EXEC dbo.MyError
GO
I have tried nesting the stored procedure in its own TRY CATCH but this leads to TRANSACTION ROLLBACK issues.
Is ERROR_PROCEDURE() NULL because it is out of scope? And is there a way to set this?
It seems the reason is due to Dynamic SQL being executed in called Stored procedure. Is there a way to handle this?
Edit based on updated question and comments
ERROR_PROCEDURE() will not return a procedure name for SQL executed via SP_EXECUTESQL. Logically, if it did, it would return 'SP_EXECUTESQL' :). See this Connect entry "TRY/CATCH: ERROR_PROCEDURE() does not report name of procedure if error occured in dynamic SQL", in particular this sentence in the response from Microsoft;
Since there is no name associated with the ad hoc SQL, ERROR_PROCEDURE
will return NULL for errors raised from the execution level of the ad
hoc SQL.
I knocked up a very quick test and it works for me (SQL Server 2012);
CREATE PROC dbo.MyError AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN
--do stuff here
--SQL CODE
--then call sproc
EXEC dbo.MyInnerError
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRANSACTION
END
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH
END
GO
CREATE PROC dbo.MyInnerError AS
BEGIN
;THROW 51000, 'This is my only error.', 1;
END
GO
EXEC dbo.MyError
GO
Result is;
ErrorNumber ErrorSeverity ErrorState ErrorProcedure ErrorLine ErrorMessage
----------- ------------- ----------- ---------------- ----------- ------------------------
51000 16 1 MyInnerError 4 This is my only error.
The issue is that the sub-proc call to MyInnerError isn't failing; it is a call within MyInnerError that is failing but yet MyInnerError is completely successfully.
MyInnerError is completing successfully because you are not trapping the error and reporting a failure like you are doing in the outer proc via the TRY / CATCH structure.
That, and errors coming from Dynamic SQL will naturally never set ERROR_PROCEDURE().
All of your procs should have the TRY / CATCH structure and the CATCH block should use either RAISERROR or THROW (depending on what version of SQL Server you are using) so that you can bubble the error up to the calling scope.
DECLARE #InNestedTransaction BIT = 0;
BEGIN TRY
IF (##TRANCOUNT > 0)
BEGIN
SET #InNestedTransaction = 1;
END;
ELSE
BEGIN
BEGIN TRAN;
END;
... one or more SQL statements ...
COMMIT;
END TRY
BEGIN CATCH
IF (#InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
IF (ERROR_PROCEDURE() IS NULL)
BEGIN
DECLARE #ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrState TINYINT = ERROR_STATE(),
#ErrSeverity TINYINT = ERROR_SEVERITY();
RAISERROR(#ErrMessage, #ErrSeverity, #ErrState);
RETURN;
END;
;THROW; -- introduced in SQL Server 2012
---- If using SQL Server 2008, replace the above (from "IF" through "THROW")
---- with the following.
-- DECLARE #ErrMessage NVARCHAR(4000) = ERROR_MESSAGE();
-- RAISERROR(#ErrMessage, 16, 1);
-- RETURN;
END CATCH;
The IF (ERROR_PROCEDURE() IS NULL) block is used to catch situations like this where there is no proc generating the error. Calling ;THROW; by itself will bubble-up the current error info, which in this case is NULL for ERROR_PROCEDURE().
If you want to test this yourself, just run the following SQL which creates 3 stored procedures, all of which use the structure shown above. The inner-most procedure (ErrorTest1) calls sp_executesql using SELECT 1/0; as the query. The "divide by zero" error is trapped by the TRY / CATCH. The ERROR_PROCEDURE() function returns NULL as it was Dynamic SQL that generated the error. So, RAISERROR is called (technically calling ;THROW 50505, #ErrMessage, #ErrState; would work as well) to indicate to the calling process that the current proc generated an error.
Test Setup:
IF (OBJECT_ID(N'ErrorTest3') IS NOT NULL)
BEGIN
DROP PROCEDURE ErrorTest3;
END;
IF (OBJECT_ID(N'ErrorTest2') IS NOT NULL)
BEGIN
DROP PROCEDURE ErrorTest2;
END;
IF (OBJECT_ID(N'ErrorTest1') IS NOT NULL)
BEGIN
DROP PROCEDURE ErrorTest1;
END;
GO
CREATE PROCEDURE dbo.ErrorTest1
AS
SET NOCOUNT ON;
DECLARE #InNestedTransaction BIT = 0;
BEGIN TRY
IF (##TRANCOUNT > 0)
BEGIN
SET #InNestedTransaction = 1;
END;
ELSE
BEGIN
BEGIN TRAN;
END;
SELECT '1a';
EXEC sp_executesql N'SELECT 1/0 AS [ForceError];';
SELECT '1b';
COMMIT;
END TRY
BEGIN CATCH
IF (#InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
IF (ERROR_PROCEDURE() IS NULL)
BEGIN
DECLARE #ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrState TINYINT = ERROR_STATE(),
#ErrSeverity TINYINT = ERROR_SEVERITY();
RAISERROR(#ErrMessage, #ErrSeverity, #ErrState);
RETURN;
END;
;THROW; -- introduced in SQL Server 2012
END CATCH;
GO
CREATE PROCEDURE dbo.ErrorTest2
AS
SET NOCOUNT ON;
DECLARE #InNestedTransaction BIT = 0;
BEGIN TRY
IF (##TRANCOUNT > 0)
BEGIN
SET #InNestedTransaction = 1;
END;
ELSE
BEGIN
BEGIN TRAN;
END;
SELECT '2a';
EXEC dbo.ErrorTest1;
SELECT '2b';
COMMIT;
END TRY
BEGIN CATCH
IF (#InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
IF (ERROR_PROCEDURE() IS NULL)
BEGIN
DECLARE #ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrState TINYINT = ERROR_STATE(),
#ErrSeverity TINYINT = ERROR_SEVERITY();
RAISERROR(#ErrMessage, #ErrSeverity, #ErrState);
RETURN;
END;
;THROW; -- introduced in SQL Server 2012
END CATCH;
GO
CREATE PROCEDURE dbo.ErrorTest3
AS
SET NOCOUNT ON;
DECLARE #InNestedTransaction BIT = 0;
BEGIN TRY
IF (##TRANCOUNT > 0)
BEGIN
SET #InNestedTransaction = 1;
END;
ELSE
BEGIN
BEGIN TRAN;
END;
SELECT '3a';
EXEC dbo.ErrorTest2;
SELECT '3b';
COMMIT;
END TRY
BEGIN CATCH
IF (#InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
SELECT ERROR_PROCEDURE() AS [ErrorProcedure],
ERROR_STATE() AS [ErrorState],
ERROR_SEVERITY() AS [ErrorSeverity];
IF (ERROR_PROCEDURE() IS NULL)
BEGIN
DECLARE #ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrState TINYINT = ERROR_STATE(),
#ErrSeverity TINYINT = ERROR_SEVERITY();
RAISERROR(#ErrMessage, #ErrSeverity, #ErrState);
RETURN;
END;
;THROW; -- introduced in SQL Server 2012
END CATCH;
GO
Run Test:
EXEC dbo.ErrorTest3;
Returns:
5 result sets:
3a
2a
1a
<empty>
ErrorProcedure ErrorState ErrorSeverity
ErrorTest1 1 16
And in the "Messages" tab:
Msg 50000, Level 16, State 1, Procedure ErrorTest1, Line 36
Divide by zero error encountered.

Resources