There was also a ROLLBACK ERROR and tSQLt.ExpectException - sql

Here is the scenario:
Stored procedure sproc_a calls sproc_b. Then sproc_b calls sproc_c. A typical nested procedure.
Sproc_a did a SET XACT_ABORT ON; and used named transaction.
Sproc_c raised an error.
tSQLt.ExpectException failed to acknowledge the error. The test should be successful but it failed.
Below is the code to replicate the scenario.
create procedure sproc_c
as
RAISERROR('An error is found', 11, 1)
go
create procedure sproc_b
as
exec dbo.sproc_c;
go
create procedure sproc_a
as
SET QUOTED_IDENTIFIER OFF
SET ANSI_NULLS ON
SET NOCOUNT ON
SET XACT_ABORT ON
SET ANSI_WARNINGS OFF
declare #transactionName as varchar(50) = '[POC]';
begin tran #transactionName
save tran #transactionName
exec dbo.sproc_b;
commit tran #transactionName
go
CREATE PROCEDURE [test sproc_a]
AS
-- Assert
BEGIN
EXEC tSQLt.ExpectException
#ExpectedMessage = 'An error is found'
END
-- Act
BEGIN
EXEC dbo.sproc_a
END
GO
EXEC tSQLt.Run '[test sproc_a]'
When I removed the SET XACT_ABORT ON, the unit test is successful but it hitches an error with it: Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
This is more like a bug report. Well I guess maybe the question is: anyone who has an idea on how to fix it? :)

Applying the logic from How to ROLLBACK a transaction when testing using tSQLt add a TRY CATCH that checks to see if a ROLLBACK is still needed.
create procedure sproc_c
as
RAISERROR('An error is found', 11, 1)
go
create procedure sproc_b
as
exec dbo.sproc_c;
go
create procedure sproc_a
as
SET QUOTED_IDENTIFIER OFF
SET ANSI_NULLS ON
SET NOCOUNT ON
SET XACT_ABORT ON
SET ANSI_WARNINGS OFF
declare #transactionName as varchar(50) = '[POC]';
BEGIN TRY
begin tran #transactionName
save tran #transactionName
exec dbo.sproc_b;
commit tran #transactionName
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK;
-- Do some exception handling
-- You'll need to reraise the error to prevent exceptions about inconsistent
-- ##TRANCOUNT before / after execution of the stored proc.
RAISERROR('An error is found', 11, 1);
END CATCH
go
CREATE PROCEDURE [test sproc_a]
AS
-- Assert
BEGIN
EXEC tSQLt.ExpectException
#ExpectedMessage = 'An error is found'
END
-- Act
BEGIN
EXEC dbo.sproc_a
END
GO
EXEC tSQLt.Run '[test sproc_a]'

I'm making a separate comment to answer my own question. I've investigated tSQLt.Private_RunTest. It turns out:
Private_RunTest has a BEGIN TRAN. Then it will save a named transaction.
It is followed by a TRY-CATCH, it will do an exec(#cmd). This basically executes your unit tests. For example: "EXEC tSQLt.Run '[test sproc_a]'".
When sproc_a raises an error, Private_RunTest will try to do a ROLLBACK TRAN #TranName. This will fail because it does not rollback the named transaction in sproc_a.
As a resolution, I hacked tSQLt.Private_RunTest and replaced the code "ROLLBACK TRAN #TranName;" with "ROLLBACK TRAN;".
I also added a condition when doing a commit.
I'm not sure what the implications are after doing this change. We'll see how it goes. Below are my changes:
CREATE PROCEDURE tSQLt.Private_RunTest
#TestName NVARCHAR(MAX),
#SetUp NVARCHAR(MAX) = NULL
AS
BEGIN
DECLARE #Msg NVARCHAR(MAX); SET #Msg = '';
DECLARE #Msg2 NVARCHAR(MAX); SET #Msg2 = '';
DECLARE #Cmd NVARCHAR(MAX); SET #Cmd = '';
DECLARE #TestClassName NVARCHAR(MAX); SET #TestClassName = '';
DECLARE #TestProcName NVARCHAR(MAX); SET #TestProcName = '';
DECLARE #Result NVARCHAR(MAX); SET #Result = 'Success';
DECLARE #TranName CHAR(32); EXEC tSQLt.GetNewTranName #TranName OUT;
DECLARE #TestResultId INT;
DECLARE #PreExecTrancount INT;
TRUNCATE TABLE tSQLt.CaptureOutputLog;
CREATE TABLE #ExpectException(ExpectException INT,ExpectedMessage NVARCHAR(MAX), ExpectedSeverity INT, ExpectedState INT, ExpectedMessagePattern NVARCHAR(MAX), ExpectedErrorNumber INT, FailMessage NVARCHAR(MAX));
IF EXISTS (SELECT 1 FROM sys.extended_properties WHERE name = N'SetFakeViewOnTrigger')
BEGIN
RAISERROR('Test system is in an invalid state. SetFakeViewOff must be called if SetFakeViewOn was called. Call SetFakeViewOff after creating all test case procedures.', 16, 10) WITH NOWAIT;
RETURN -1;
END;
SELECT #Cmd = 'EXEC ' + #TestName;
SELECT #TestClassName = OBJECT_SCHEMA_NAME(OBJECT_ID(#TestName)), --tSQLt.Private_GetCleanSchemaName('', #TestName),
#TestProcName = tSQLt.Private_GetCleanObjectName(#TestName);
INSERT INTO tSQLt.TestResult(Class, TestCase, TranName, Result)
SELECT #TestClassName, #TestProcName, #TranName, 'A severe error happened during test execution. Test did not finish.'
OPTION(MAXDOP 1);
SELECT #TestResultId = SCOPE_IDENTITY();
BEGIN TRAN;
SAVE TRAN #TranName;
SET #PreExecTrancount = ##TRANCOUNT;
TRUNCATE TABLE tSQLt.TestMessage;
DECLARE #TmpMsg NVARCHAR(MAX);
BEGIN TRY
IF (#SetUp IS NOT NULL) EXEC #SetUp;
EXEC (#Cmd);
IF(EXISTS(SELECT 1 FROM #ExpectException WHERE ExpectException = 1))
BEGIN
SET #TmpMsg = COALESCE((SELECT FailMessage FROM #ExpectException)+' ','')+'Expected an error to be raised.';
EXEC tSQLt.Fail #TmpMsg;
END
END TRY
BEGIN CATCH
IF ERROR_MESSAGE() LIKE '%tSQLt.Failure%'
BEGIN
SELECT #Msg = Msg FROM tSQLt.TestMessage;
SET #Result = 'Failure';
END
ELSE
BEGIN
DECLARE #ErrorInfo NVARCHAR(MAX);
SELECT #ErrorInfo =
COALESCE(ERROR_MESSAGE(), '<ERROR_MESSAGE() is NULL>') +
'[' +COALESCE(LTRIM(STR(ERROR_SEVERITY())), '<ERROR_SEVERITY() is NULL>') + ','+COALESCE(LTRIM(STR(ERROR_STATE())), '<ERROR_STATE() is NULL>') + ']' +
'{' + COALESCE(ERROR_PROCEDURE(), '<ERROR_PROCEDURE() is NULL>') + ',' + COALESCE(CAST(ERROR_LINE() AS NVARCHAR), '<ERROR_LINE() is NULL>') + '}';
IF(EXISTS(SELECT 1 FROM #ExpectException))
BEGIN
DECLARE #ExpectException INT;
DECLARE #ExpectedMessage NVARCHAR(MAX);
DECLARE #ExpectedMessagePattern NVARCHAR(MAX);
DECLARE #ExpectedSeverity INT;
DECLARE #ExpectedState INT;
DECLARE #ExpectedErrorNumber INT;
DECLARE #FailMessage NVARCHAR(MAX);
SELECT #ExpectException = ExpectException,
#ExpectedMessage = ExpectedMessage,
#ExpectedSeverity = ExpectedSeverity,
#ExpectedState = ExpectedState,
#ExpectedMessagePattern = ExpectedMessagePattern,
#ExpectedErrorNumber = ExpectedErrorNumber,
#FailMessage = FailMessage
FROM #ExpectException;
IF(#ExpectException = 1)
BEGIN
SET #Result = 'Success';
SET #TmpMsg = COALESCE(#FailMessage+' ','')+'Exception did not match expectation!';
IF(ERROR_MESSAGE() <> #ExpectedMessage)
BEGIN
SET #TmpMsg = #TmpMsg +CHAR(13)+CHAR(10)+
'Expected Message: <'+#ExpectedMessage+'>'+CHAR(13)+CHAR(10)+
'Actual Message : <'+ERROR_MESSAGE()+'>';
SET #Result = 'Failure';
END
IF(ERROR_MESSAGE() NOT LIKE #ExpectedMessagePattern)
BEGIN
SET #TmpMsg = #TmpMsg +CHAR(13)+CHAR(10)+
'Expected Message to be like <'+#ExpectedMessagePattern+'>'+CHAR(13)+CHAR(10)+
'Actual Message : <'+ERROR_MESSAGE()+'>';
SET #Result = 'Failure';
END
IF(ERROR_NUMBER() <> #ExpectedErrorNumber)
BEGIN
SET #TmpMsg = #TmpMsg +CHAR(13)+CHAR(10)+
'Expected Error Number: '+CAST(#ExpectedErrorNumber AS NVARCHAR(MAX))+CHAR(13)+CHAR(10)+
'Actual Error Number : '+CAST(ERROR_NUMBER() AS NVARCHAR(MAX));
SET #Result = 'Failure';
END
IF(ERROR_SEVERITY() <> #ExpectedSeverity)
BEGIN
SET #TmpMsg = #TmpMsg +CHAR(13)+CHAR(10)+
'Expected Severity: '+CAST(#ExpectedSeverity AS NVARCHAR(MAX))+CHAR(13)+CHAR(10)+
'Actual Severity : '+CAST(ERROR_SEVERITY() AS NVARCHAR(MAX));
SET #Result = 'Failure';
END
IF(ERROR_STATE() <> #ExpectedState)
BEGIN
SET #TmpMsg = #TmpMsg +CHAR(13)+CHAR(10)+
'Expected State: '+CAST(#ExpectedState AS NVARCHAR(MAX))+CHAR(13)+CHAR(10)+
'Actual State : '+CAST(ERROR_STATE() AS NVARCHAR(MAX));
SET #Result = 'Failure';
END
IF(#Result = 'Failure')
BEGIN
SET #Msg = #TmpMsg;
END
END
ELSE
BEGIN
SET #Result = 'Failure';
SET #Msg =
COALESCE(#FailMessage+' ','')+
'Expected no error to be raised. Instead this error was encountered:'+
CHAR(13)+CHAR(10)+
#ErrorInfo;
END
END
ELSE
BEGIN
SET #Result = 'Error';
SET #Msg = #ErrorInfo;
END
END;
END CATCH
BEGIN TRY
-- Replaced "ROLLBACK TRAN #TranName;" with "ROLLBACK TRAN;". The prior approach can't handle nested named transactions.
--ROLLBACK TRAN #TranName;
ROLLBACK TRAN;
END TRY
BEGIN CATCH
DECLARE #PostExecTrancount INT;
SET #PostExecTrancount = #PreExecTrancount - ##TRANCOUNT;
IF (##TRANCOUNT > 0) ROLLBACK;
BEGIN TRAN;
IF( #Result <> 'Success'
OR #PostExecTrancount <> 0
)
BEGIN
SELECT #Msg = COALESCE(#Msg, '<NULL>') + ' (There was also a ROLLBACK ERROR --> ' + COALESCE(ERROR_MESSAGE(), '<ERROR_MESSAGE() is NULL>') + '{' + COALESCE(ERROR_PROCEDURE(), '<ERROR_PROCEDURE() is NULL>') + ',' + COALESCE(CAST(ERROR_LINE() AS NVARCHAR), '<ERROR_LINE() is NULL>') + '})';
SET #Result = 'Error';
END
END CATCH
If(#Result <> 'Success')
BEGIN
SET #Msg2 = #TestName + ' failed: (' + #Result + ') ' + #Msg;
EXEC tSQLt.Private_Print #Message = #Msg2, #Severity = 0;
END
IF EXISTS(SELECT 1 FROM tSQLt.TestResult WHERE Id = #TestResultId)
BEGIN
UPDATE tSQLt.TestResult SET
Result = #Result,
Msg = #Msg
WHERE Id = #TestResultId;
END
ELSE
BEGIN
INSERT tSQLt.TestResult(Class, TestCase, TranName, Result, Msg)
SELECT #TestClassName,
#TestProcName,
'?',
'Error',
'TestResult entry is missing; Original outcome: ' + #Result + ', ' + #Msg;
END
-- Add "IF (##TRANCOUNT > 0)" so that it will only do the commit if there is a transaction.
IF (##TRANCOUNT > 0)
COMMIT;
END;

Related

Exception Handling Not working in SQL SERVER

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

Problems with ROLLBACK TRANSACTION inside try/catch

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

does it run anything after a catch block?

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;

SQL Server Try Catch

I have a stored procedure that is run by the SQL Agent every x minutes. The stored procedure has a while loop that reads each row and does something with them.
I want to handle errors as they occur in the while loop. I need to use Throw in the CATCH block because then SQL Server Agent will not notify if an error occurred.
But the problem is if I use the throw block it breaks out of the while loop but does not process the other records.
How can I use TRY CATCH in a while loop and if an error occurs it should continue with while loop?
This is my code:
WHILE #i<#Count OR #Count IS NULL
BEGIN
SELECT #Id = NULL --Clear variable
SELECT TOP 1
#Id = Id,
#TableNo = [TableNo],
#Action = [Action],
#RecId = [RecId],
#NDB = dbo.GetDB(CId)
FROM
dbo.alot WITH (NOLOCK)
WHERE
CId = #Cid
AND Error = 0
AND (#Table IS NULL OR TableNo = #Table)
AND Tableno <> 50109
ORDER BY
Id
IF #Id IS NOT NULL
BEGIN
SELECT
#SQL = N'EXECUTE #RC = ['+dbo.GetDB(#CId)+'].[dbo].[alot_web] #TableNo, #Action, #RecId, #NaviDB'
BEGIN TRY
IF #RecId = '0-761345-27353-4'
BEGIN
SELECT 1 / 0; -- Generate an error here.
END
EXEC master.dbo.sp_executesql #SQL, N'#TableNo nvarchar(12), #Action tinyint, #RecId nvarchar(36), #NDB varchar(12), #RC int OUTPUT'
, #TableNo, #Action, #RecId, #NaviDB, #Rc OUTPUT
END TRY
BEGIN CATCH
DECLARE #Description VARCHAR(1024);
SELECT #Description = 'WebQueue ID: ' + ISNULL(CAST(#Id AS VARCHAR), '') + ' CompanyID: ' + ISNULL(#Cid, '') + ' Action: ' + ISNULL(CAST(#Action AS VARCHAR), '') + ' RecID: ' + ISNULL(CAST(#RecId AS VARCHAR), '') + ' #RC: ' + ISNULL(CAST(#RC AS VARCHAR), '');
EXEC dbo.LogError #Description;
THROW;
END CATCH
IF #RC = 0 AND ##ERROR = 0
BEGIN
IF EXISTS(SELECT * FROM Queue
WHERE CId = #CId AND [Action] = #Action
AND TableNo = #Tableno AND RecId = #RecID AND Id <> #Id)
BEGIN
DELETE FROM Queue
WHERE CId = #CId AND [Action] = #Action AND TableNo = #Tableno
AND RecId = #RecID
SELECT #Ok += ##ROWCOUNT
END
ELSE BEGIN
DELETE FROM Queue WHERE Id = #Id
SELECT #Ok += ##ROWCOUNT
END
END
ELSE BEGIN
IF EXISTS(SELECT * FROM Queue
WHERE CId = #CId AND [Action] = #Action
AND TableNo = #Tableno AND RecId = #RecID AND Id <> #Id)
BEGIN
UPDATE Queue
SET Error = 1
WHERE CId = #CId AND [Action] = #Action AND TableNo = #Tableno AND RecId = #RecID
SELECT #Failed += ##ROWCOUNT
END
ELSE BEGIN
UPDATE Queue
SET Error = 1
WHERE Id = #Id
SELECT #Failed += ##ROWCOUNT
END
END
END
ELSE
BREAK
SELECT #i += 1
/*IF #i>0 BEGIN--logging >>
INSERT INTO [AdminDB].[dbo].[Replication_Log] ([Date],[CId],[Loops],[DurationSS],[Ok],[Failed])
SELECT Getdate(),#CId,#i,DATEDIFF(ss,#Startdt,getdate()),#Ok,#Failed
END */
END
Replace your THROW with CONTINUE; this way the next record will be processed without Canceling the code.
(EDITED!)
since you log your errors with
EXEC dbo.LogError #Description;
i think you don't need to rethrow the error. Since you stated you don't want the program to end.
example of CONTINUE:
DECLARE #intFlag INT
SET #intFlag = 1
WHILE (#intFlag <=5)
BEGIN
PRINT #intFlag
SET #intFlag = #intFlag + 1
CONTINUE;
IF #intFlag = 4 -- This will never executed
BREAK;
END
GO
source : http://blog.sqlauthority.com/2007/10/24/sql-server-simple-example-of-while-loop-with-continue-and-break-keywords/
EDIT:
for your job agent:
create a new variable at the top of your procedure :
int #ErrorAmount = 0
your catch would need to look like this:
BEGIN CATCH
DECLARE #Description VARCHAR(1024);
SELECT #Description = 'WebQueue ID: ' + ISNULL(CAST(#Id AS VARCHAR), '') + ' CompanyID: ' + ISNULL(#Cid, '') + ' Action: ' + ISNULL(CAST(#Action AS VARCHAR), '') + ' RecID: ' + ISNULL(CAST(#RecId AS VARCHAR), '') + ' #RC: ' + ISNULL(CAST(#RC AS VARCHAR), '');
EXEC dbo.LogError #Description;
SET #ErrorAmount = #ErrorAmount+1; --add this line
CONTINUE; --REPLACE THROW with CONTINUE
END CATCH
at the end of your procedure Add
if #ErrorAmount > 0
BEGIN
THROW 6000,'Your message' , 1;
END
this way your Agent will show you the error and your whole process still did the job.
Your THROW raises an exception which causes the SP to stop processing. Essentially you are catching any errors that occur but rethrowing them which will indeed break out of the while loop.
Remove it and the sp should then just continue as normal.
My suggestion is use one more loop outside the loop you have.
So the TSQL will be something like this, will this helps?
DECLARE #Continue bit,
#i INT,
#Count INT
SELECT #i = 1 ,
#Count =10
SET #Continue =1
While #Continue=1
BEGIN
BEGIN TRY
--YOUR existing while loop
WHILE #i<#Count OR #Count IS NULL
BEGIN
-- The rest of your code
IF #i=3
BEGIN
set #i= #i/0
END
PRINT #i
SET #i=#i+1
--Set to false when complete
IF #i =#Count
SET #Continue =0
END
END TRY
BEGIN CATCH
print ERROR_MESSAGE()
set #i =#i+1
END CATCH
END
Expected result from above
1
2
Divide by zero error encountered.
4
5
6
7
8
9
Not all errors can be caught by TRY/CATCH. In this case, sp_start_job actually calls external procedures, and these are outside the bounds of SQL Server's error handling. Or at least that's the story that they're sticking to:
http://connect.microsoft.com/SQLServer/feedback/details/362112/sp-start-job-error-handling
Please refer this for more details
You could:
Remove the THROW. Your script will continue as normal and errors will be logged
At the end of the script check #Failed and if appropriate use RAISEERROR which will be noted by the SQL Agent

do you see any problem with that stored procedure template?

so I created that(I used some stuff found on other website) to handle transactions and having a sort of stacktrace while executing stored procedure that could call other stored procedure that need transaction and etc.
so if I have A calling B and B is calling C and C got an error, I can correctly rollback my stuff and returning a stacktrace saying: error in C follow the trace to find out where/how/etc...
do any of you find a problem with this logic?
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[NAME]
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT ON
declare #trancount int
set #trancount = ##trancount
declare #savePointName varchar(40)
set #savePointName = newid()
BEGIN TRY
if #trancount = 0
begin transaction
else
save transaction #savePointName
/*
// STUFF HERE
*/
if #trancount = 0
commit transaction
END TRY
BEGIN CATCH
declare #xstate int
set #xstate = XACT_STATE()
if #xstate = -1 and #trancount = 0
rollback transaction
if #xstate = 1 and #trancount = 0
rollback transaction
if #xstate = 1 and #trancount > 0
rollback transaction #savePointName
declare #message varchar(max)
set #message = ERROR_MESSAGE() +
' (' + ERROR_PROCEDURE() +
':' + ltrim(str(ERROR_LINE())) +
', Raised ' + ltrim(str(ERROR_NUMBER())) +
', Severity ' + ltrim(str(ERROR_SEVERITY())) +
', State ' + ltrim(str(ERROR_STATE())) + ')'
RAISERROR(#message,16,1)
END CATCH
END
No, I can't spot anything wrong with this code.