I'm trying to execute a sproc but I'm not sure if I'm going in the right direction. When my IF conditions are true it will not print my raiseerrors...
set transaction isolation level repeatable read
declare #return_value int = 0
declare #someValue int = 3
SET #retry = 3;
--Keep trying to update
--table if this task is
--selected as the deadlock
--victim.
WHILE (#retry > 0)
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
--check someValue
if #someValue < 5
begin
raiserror ('number is less than 5', 16,1)
ROLLBACK TRANSACTION
return 99
end
--all o.k , set retry 0 ending the while, commit transaction--
SET #retry = 0;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
RAISERROR ('Errors found, please fix these errors and retry. Transaction Rolled back', 16, 2);
-- Check error number.
-- If deadlock victim error,
-- then reduce retry count
-- for next update retry.
-- If some other error
-- occurred, then exit
-- retry WHILE loop.
IF (ERROR_NUMBER() = 1205)
SET #retry = #retry - 1;
ELSE
SET #retry = -1;
IF XACT_STATE() <> 0
ROLLBACK TRANSACTION;
END CATCH;
END; -- End WHILE loop.
The first RaIsError will be consumed by the catch block. If you want to preserve it, and perhaps add additional information, you can do something like this:
set xact_abort, nocount on;
set transaction isolation level repeatable read; -- Scope is this stored procedure.
declare #LocalTransaction as Bit = case when ##TranCount = 0 then 1 else 0 end;
declare #Return_Value as Int = 0;
declare #SomeValue int = 3;
declare #Retry as Int = 3;
while #Retry > 0
begin
begin try
if #LocalTransaction = 1
begin transaction;
if #SomeValue < 5
RaIsError ( 'Number is less than 5.', 16, 1 );
set #Retry = 0;
if #LocalTransaction = 1
commit transaction;
end try
begin catch
if Error_Number() = 1205
set #Retry -= 1;
else
begin
set #Retry = -1;
-- Save the exception.
declare #ErrorLine as Int = Error_Line();
declare #ErrorMessage as NVarChar(4000) = Error_Message();
declare #ErrorNumber as Int = Error_Number();
declare #ErrorProcedure as NVarChar(126) = Error_Procedure();
declare #ErrorSeverity as Int = Error_Severity();
declare #ErrorState as Int = Error_State();
declare #NewLine as Char(2) = Char( 13 ) + Char( 10 ); -- '\r\n'.
-- Rollback only transactions when there is an active transaction that we started.
if Xact_State() <> 0 and #LocalTransaction = 1
rollback transaction;
-- Exit with the exception.
RaIsError( '%s%s#%i [Proc: %s/Line %i]', #ErrorSeverity, #ErrorState, #ErrorMessage, #NewLine, #ErrorNumber, #ErrorProcedure, #ErrorLine );
return 99;
end;
end catch;
end;
Note that the error return is also handled by the catch block since code in the try block after the RaIsError shouldn't execute.
Related
I'm doing a code review for a new procedure in sql
and i have two question please:
The commit transaction is encapsulated in the whole procedure - i think it should be only in the insert statement.
If there is a begin transaction, there should be also a error handling.
I am adding the code before
BEGIN
DECLARE #paymentSpecificationId BIGINT ;
BEGIN TRANSACTION
SET #paymentSpecificationId = (SELECT Id FROM [dbo].[PaymentSpecifications]
WHERE PaymentSpecificationGuid = #PaymentSpecificationGuid) ;
IF(#paymentSpecificationId > 0)
BEGIN
SELECT #paymentSpecificationId AS Results;
COMMIT TRANSACTION;
RETURN;
END
INSERT INTO [dbo].[PaymentSpecifications]
(
[CurrencyBalanceId],
[PaymentSpecificationGuid],
[PaymentSpecificationTypeId],
[ExternalId],
[Reference],
[ExternalOriginatorId],
[EventTimestamp],
[Created]
)
VALUES
(
#CurrencyBalanceId,
#PaymentSpecificationGuid,
#PaymentSpecificationTypeId,
#ExternalId,
#Reference,
#ExternalOriginatorId,
#EventTimestamp,
#Created
)
SELECT SCOPE_IDENTITY() AS Results;
COMMIT TRANSACTION;
END
and the code after my suggestion fix
BEGIN
BEGIN TRY
DECLARE #paymentSpecificationId BIGINT ;
SET #paymentSpecificationId = (SELECT Id FROM [dbo].[PaymentSpecifications]
WHERE PaymentSpecificationGuid = #PaymentSpecificationGuid) ;
IF(#paymentSpecificationId > 0)
BEGIN
SELECT #paymentSpecificationId AS Results;
RETURN;
END
BEGIN TRANSACTION
INSERT INTO [dbo].[PaymentSpecifications]
(
[CurrencyBalanceId],
[PaymentSpecificationGuid],
[PaymentSpecificationTypeId],
[ExternalId],
[Reference],
[ExternalOriginatorId],
[EventTimestamp],
[Created]
)
VALUES
(
#CurrencyBalanceId,
#PaymentSpecificationGuid,
#PaymentSpecificationTypeId,
#ExternalId,
#Reference,
#ExternalOriginatorId,
#EventTimestamp,
#Created
)
SELECT SCOPE_IDENTITY() AS Results;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
DECLARE #ErrMsg nvarchar(4000), #ErrSeverity int
SELECT #ErrMsg = ERROR_MESSAGE(),
#ErrSeverity = ERROR_SEVERITY()
RAISERROR(#ErrMsg, #ErrSeverity, 1)
END CATCH
END
GO
thanks
How to handle error in a recursive procedure? I'm using SQL Server 2012.
CREATE PROCEDURE [dbo].[TEST]
#i INT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION [T]
BEGIN TRY
PRINT #i
IF #i = 10
BEGIN
COMMIT TRANSACTION [T]
RETURN;
END
ELSE
BEGIN
SET #i = #i + 1;
EXEC DBO.TEST #i;
END
COMMIT TRANSACTION [T]
END TRY
BEGIN CATCH
IF ##TRANCOUNT>0
ROLLBACK TRANSACTION [T]
SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() AS ErrorMessage;
END CATCH
END
This is only a example, because I'm not authorized to post original code.
CREATE PROCEDURE [dbo].[TEST]
#i INT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE #InNestedTransaction BIT;
BEGIN TRY
IF (##TRANCOUNT = 0)
BEGIN
SET #InNestedTransaction = 0;
BEGIN TRAN; -- only start a transaction if not already in one
END;
ELSE
BEGIN
SET #InNestedTransaction = 1;
END;
/*********************************************/
PRINT #i
IF #i = 8
BEGIN
DECLARE #ForceError INT;
--set #ForceError = 1/0; -- uncomment this line to force error.
END
IF #i = 10
BEGIN
RETURN;
END
ELSE
BEGIN
SET #i = #i + 1;
EXEC DBO.TEST #i;
END
/*********************************************/
IF (##TRANCOUNT > 0 AND #InNestedTransaction = 0)
BEGIN
COMMIT;
END;
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0 AND #InNestedTransaction = 0)
BEGIN
ROLLBACK;
SELECT ERROR_NUMBER() AS ErrorNumber, ERROR_MESSAGE() AS ErrorMessage;
END;
ELSE
BEGIN
THROW;
END
END CATCH
END
we have a below procedure to update/insert a table. I have added exception handling in stored procedure as below.
CREATE PROCEDURE USP_UPDATESP
#Workstationlist worktable READONLY
AS
BEGIN
SET NOCOUNT ON
DECLARE #rerror As int
SET #rerror = 0
BEGIN TRY
BEGIN TRAN
MERGE [dbo].WORKTABLE AS [ofc]
USING #Workstationlist AS [Source] ON [ofc].officeid = [Source].id
WHEN MATCHED THEN
UPDATE
SET NumWorkStations = [Source].wsno,
ModifiedBy = [Source].modifiedby,
ModifiedByUsername = [Source].modifieduser,
ModifiedDate = GETDATE()
WHEN NOT MATCHED THEN
INSERT ( officeid, NumWorkStations, ModifiedBy, ModifiedByUsername, ModifiedDate )
VALUES ([Source].ID,[Source].wsno, [Source].modifiedby, [Source].modifieduser,GETDATE() );
SET #rerror = #rerror + ##error
If #rerror = 0
BEGIN
COMMIT TRAN
END
END TRY
BEGIN CATCH
SELECT #rerror AS ErrNum
ROLLBACK TRAN
End Catch
SET NOCOUNT off
END
GO
When I execute the procedure with an exception (passing null to id column) as below
declare #Workstationlist worktable
insert into #Workstationlist VALUES ( NULL,500,106720,106720)
EXEC USP_UPDATESP #Workstationlist
I got #error as 0 Always . Is there any problem this way of error handling?
to me it looks like you are mixing TRY...CATCH error handling with old style error handling.
the code:
SET #rerror = #rerror + ##error
cannot be reached because when an exception occurs the control is passed to the catch block so the #rerror variable will always be 0, the value initially set.
in the catch block you should leverage the proper structures/objects to access error information and drop all the old way completely.
something like this:
BEGIN CATCH;
DECLARE #ErrSev INT,
#ErrMsg NVARCHAR(MAX),
#ErrState INT;
SELECT #ErrSev = ERROR_SEVERITY(),
#ErrState = ERROR_STATE(),
#ErrMsg = isnull(ERROR_PROCEDURE(), '(unknown procedure)') + ': ' + isnull(ERROR_MESSAGE(), '(unknown message)');
RAISERROR(#ErrMsg, #ErrSev, #ErrState);
END CATCH;
Catch the error in proper way.
BEGIN TRY
BEGIN TRANSACTION;
COMMIT TRANSACTION;
SELECT 'Success' AS Result
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
SELECT 'Failed' AS Result
,ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage
END CATCH;
END
I'm having this error when I try to execute this code:
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
I know the problem is on the sp [dbo].[QueueInsert], the sp has an error and it goes directly to the catch. It's funny because the first one (EXEC [dbo].[BacthInsert]) is inserting a summary record and after running the sp, I see the record, so is doing a commit and not doing a rollback. What's wrong with this code? thanks!
CREATE PROCEDURE [cnfg].[SendEmail]
(
,#Employees [dbo].[Employees] readonly
,#ApplicationName NVARCHAR(256)
,#ErrorMsg NVARCHAR(300) = NULL OUTPUT
)
AS
BEGIN
DECLARE #ReturnVal INT
DECLARE #ApplicationId UNIQUEIDENTIFIER
DECLARE #NewIdBatch INT
DECLARE #ID INT
DECLARE #EmployeeId INT
DECLARE #Index INT = 1
DECLARE #Total INT
SET NOCOUNT ON;
SET XACT_ABORT ON;
SET #ReturnVal = 0;
SET #ErrorMsg = '';
SET #ApplicationId = [GetId](#ApplicationName);
IF (#ApplicationId IS NULL)
BEGIN
SET #ReturnVal = 1;
SET #ErrorMsg = 'The Application Name does not exist in the database';
Goto ProcedureExit
END
----------------------------------------------------------
BEGIN TRY -- Start Main TRY
----------------------------------------------------------
BEGIN TRANSACTION;
EXEC [dbo].[BacthInsert]
#ParameterId = 1
,#ID = #NewSendEmailBatchId OUTPUT
,#ErrorMsg = #ErrorMsg OUTPUT
IF ( #ErrorMsg <> '' )
BEGIN
SET #ReturnVal = 1;
SET #ErrorMsg = 'There was an error trying to insert data into [dbo].[BacthInsert] table';
RAISERROR(#ErrorMsg, 16, 1)
END
SELECT ROW_NUMBER() OVER ( ORDER BY EmployeeId ) Row,
EmployeeId
INTO #EmpIds
FROM #Employees
SELECT #Total = COUNT(*) FROM #EmpIds
WHILE ( #Index <= #Total )
BEGIN
SELECT #EmployeeId=EmployeeId FROM #EmpIds WHERE Row = #Index
EXEC [dbo].[QueueInsert]
#SendEmailBatchId = #NewIdBatch
,#ID = #ID OUTPUT
,#ErrorMsg = #ErrorMsg OUTPUT
IF ( #ErrorMsg <> '' )
BEGIN
SET #ReturnVal = 1;
SET #ErrorMsg = 'There was an error trying to insert data into [dbo].[QueueInsert] table';
RAISERROR(#ErrorMsg, 16, 1)
END
SET #Index+=1;
END
COMMIT TRANSACTION;
----------------------------------------------------------
END TRY -- End Main TRY
----------------------------------------------------------
----------------------------------------------------------
BEGIN CATCH -- Start Main CATCH
----------------------------------------------------------
SELECT ERROR_MESSAGE()
IF (XACT_STATE()) = -1
BEGIN
ROLLBACK TRANSACTION;
END;
----------------------------------------------------------
END CATCH -- End Main CATCH
----------------------------------------------------------
ProcedureExit:
RETURN #ReturnVal;
END
I'm working with SQL Server Express 2012 and I have this stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[usp_AsyncExecActivated]
as
begin
set nocount on;
declare #h uniqueidentifier
, #messageTypeName sysname
, #messageBody varbinary(max)
, #xmlBody xml
, #procedureName sysname
, #startTime datetime
, #finishTime datetime
, #execErrorNumber int
, #execErrorMessage nvarchar(2048)
, #xactState smallint
, #token uniqueidentifier;
begin transaction;
begin try;
receive top(1)
#h = [conversation_handle]
, #messageTypeName = [message_type_name]
, #messageBody = [message_body]
from [AsyncExecQueue];
if (#h is not null)
begin
if (#messageTypeName = N'DEFAULT')
begin
-- The DEFAULT message type is a procedure invocation.
-- Extract the name of the procedure from the message body.
--
select #xmlBody = CAST(#messageBody as xml);
select #procedureName = #xmlBody.value(
'(//procedure/name)[1]'
, 'sysname');
update dbo.Configurations with (serializable) set conf_value = 1
where sp_name = #procedureName
if ##rowcount = 0
begin
insert dbo.Configurations(sp_name, conf_value) values (#procedureName, 1)
end
save transaction usp_AsyncExec_procedure;
select #startTime = GETUTCDATE();
begin try
exec #procedureName;
end try
begin catch
-- This catch block tries to deal with failures of the procedure execution
-- If possible it rolls back to the savepoint created earlier, allowing
-- the activated procedure to continue. If the executed procedure
-- raises an error with severity 16 or higher, it will doom the transaction
-- and thus rollback the RECEIVE. Such case will be a poison message,
-- resulting in the queue disabling.
--
select #execErrorNumber = ERROR_NUMBER(),
#execErrorMessage = ERROR_MESSAGE(),
#xactState = XACT_STATE();
if (#xactState = -1)
begin
rollback;
raiserror(N'Unrecoverable error in procedure %s: %i: %s', 16, 10,
#procedureName, #execErrorNumber, #execErrorMessage);
end
else if (#xactState = 1)
begin
rollback transaction usp_AsyncExec_procedure;
end
end catch
select #finishTime = GETUTCDATE();
select #token = [conversation_id]
from sys.conversation_endpoints
where [conversation_handle] = #h;
if (#token is null)
begin
raiserror(N'Internal consistency error: conversation not found', 16, 20);
end
update [AsyncExecResults] set
[start_time] = #starttime
, [finish_time] = #finishTime
, [error_number] = #execErrorNumber
, [error_message] = #execErrorMessage
where [token] = #token;
if (0 = ##ROWCOUNT)
begin
raiserror(N'Internal consistency error: token not found', 16, 30);
end
end conversation #h;
end
else if (#messageTypeName = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
begin
end conversation #h;
end
else if (#messageTypeName = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
begin
declare #errorNumber int
, #errorMessage nvarchar(4000);
select #xmlBody = CAST(#messageBody as xml);
with xmlnamespaces (DEFAULT N'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
select #errorNumber = #xmlBody.value ('(/Error/Code)[1]', 'INT'),
#errorMessage = #xmlBody.value ('(/Error/Description)[1]', 'NVARCHAR(4000)');
-- Update the request with the received error
select #token = [conversation_id]
from sys.conversation_endpoints
where [conversation_handle] = #h;
update [AsyncExecResults] set
[error_number] = #errorNumber
, [error_message] = #errorMessage
where [token] = #token;
end conversation #h;
end
else
begin
raiserror(N'Received unexpected message type: %s', 16, 50, #messageTypeName);
end
end
commit;
end try
begin catch
declare #error int
, #message nvarchar(2048);
select #error = ERROR_NUMBER()
, #message = ERROR_MESSAGE()
, #xactState = XACT_STATE();
if (#xactState <> 0)
begin
rollback;
end;
update dbo.Configurations with (serializable)
set conf_value = 0
where sp_name = #procedureName
raiserror(N'Error: %i, %s', 1, 60, #error, #message) with log;
end catch
update dbo.Configurations with (serializable) set conf_value = 0
where sp_name = #procedureName
end
I have to do this:
update dbo.Configurations with (serializable)
set conf_value = 0
where sp_name = #procedureName
Every time before stored procedure ends. I'll check dbo.Configurations to see if usp_AsyncExecActivated is running or not.
Do I have to add that update on CATCH BLOCK and after the CATCH BLOCK?
I'm not sure if after catch block runs anything else or it ends stored procedure execution.
It depends on severity, if there's a session sborting error it will stop executing the procedure. Otherwise, it will continue after CATCH block. See this simplified example:
create proc x
as
begin try
select 1/0
end try
begin catch
select error_message()
raiserror (N'Received unexpected message type', 16, 50);
end catch
select 'after catch'
go
exec x;