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
Related
I'm creating a stored procedure with the following code:
CREATE OR ALTER PROCEDURE Insert_Into_Table
#Origin VARCHAR(255),
#Destination VARCHAR(255),
#Origin_Columns VARCHAR (4000),
#Destination_Columns VARCHAR (4000),
#Delete_Date_Column_Name VARCHAR(63), --Nullable
#Delete_Period_Months INT -- Nullable
AS
BEGIN TRY
DECLARE #insert_query VARCHAR(4000) = NULL;
DECLARE #delete_query VARCHAR(4000) = NULL;
DECLARE #check_query VARCHAR (4000) = NULL;
DECLARE #Date_To_Delete DATETIME = CAST(DATEADD(MONTH, -#Delete_Period_Months, GETDATE()) AS DATETIME);
-- Table names cannot be referenced directly in SPs, so we bypass
-- this issue by declaring a variable containing the query
IF #Delete_Date_Column_Name IS NOT NULL
OR #Delete_Period_Months IS NOT NULL
SET #delete_query = 'DELETE FROM ' + #Destination + ' WHERE ' +
#Delete_Date_Column_Name + ' >= ' + CONCAT(CHAR(39),CAST(#Date_To_Delete AS VARCHAR(255)),CHAR(39));
ELSE
PRINT N'Missing or no values provided for table deletion. Only executing copy';
CREATE TABLE #temptable (count INT)
SET #check_query = 'INSERT INTO #temptable SELECT TOP 1 1 AS count FROM ' + #Origin
EXECUTE(#check_query)
SET #insert_query = 'INSERT INTO' + QUOTENAME(#Destination) + QUOTENAME(#Destination_Columns, '()') +
'SELECT ' + #Origin_Columns + ' FROM ' + QUOTENAME(#Origin);
BEGIN TRY
IF EXISTS (SELECT TOP 1 * FROM #temptable)
BEGIN TRANSACTION
EXECUTE(#delete_query);
EXECUTE(#insert_query);
COMMIT
END TRY
BEGIN CATCH
ROLLBACK;
THROW 51000, 'The Origin table is empty', 1;
END CATCH
END TRY
BEGIN CATCH
THROW 51000, 'Error creating transaction', 1;
END CATCH
GO
When executing the stored proecedure with the parameters shown, it works correctly:
EXEC Insert_Into_Table
#Origin = 'Sink_Proc',
#Destination = 'Sink_Test',
#Origin_Columns = 'customer, order_number, created_date',
#Destination_Columns = 'customer, order_number, created_date',
#Delete_Date_Column_Name = NULL,
#Delete_Period_Months = NULL
However, when executing it with 25+ columns as Origin/Destination columns, when I print the #insert_query variable, it returns NULL and no operation is done. Why is this happening?
After Running this query, I am getting one custom Error :
Debit account balance can not be less than 0. somemail#7dmail.com/123/xxx/123456
And Two regular errors :
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 2, current count = 0.
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
I think, that transaction errors happen because something is throwing exception before transaction is committed.
Custom Error is from another query(AddJournalEntry) which is written below. I can not see connection between these two queries.
Query:
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE PROCEDURE [dbo].[RevokeOrder]
#OrderId int = null
--,#EntryId int OUTPUT
AS
declare #OrderTypeId nvarchar(30)
declare #MasterEntryId int
declare #NewEntryId int
declare #CustomerGuid uniqueidentifier
declare #Debit int
declare #Credit int
declare #Explanation nvarchar(100)
declare #Amount decimal(18, 8)
declare #AmountFilled decimal(18, 8)
declare #Total decimal(18, 8)
declare #TotalLeft decimal(18, 8)
declare #AmountLeft decimal(18, 8)
declare #AssetId nvarchar(30)
declare #QuoteAssetId nvarchar(30)
declare #QuotePrice decimal(18, 8)
declare #AssetReserveAccountId int
declare #AssetAccountId int
declare #EntryAmount decimal(18, 8)
BEGIN;
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET XACT_ABORT ON;
SET NOCOUNT ON;
print ''
print '++++++++++++++++++++++++++++++++++++++++++++++++'
print 'Start RevokeOrder procedure'
print '++++++++++++++++++++++++++++++++++++++++++++++++'
print ''
begin tran
select
#OrderTypeId = OrderTypeId,
#CustomerGuid = CustomerGuid,
#Amount = Amount,
#AmountFilled = AmountFilled,
#AssetId = AssetId,
#QuoteAssetId = QuoteAssetId,
#QuotePrice = QuotePrice,
#Total = Total,
#TotalLeft = TotalLeft
from dbo.[vOrder] WITH (HOLDLOCK, ROWLOCK)
where OrderId = #OrderId
if ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback
raiserror ('Order not found', 16, 1)
return 3
end
set #AmountLeft = #Amount - ISNULL(#AmountFilled, 0)
if (#OrderTypeId = 'buy')
begin
--declare #Released decimal(18, 8)
--select #Released = coalesce(SUM(Total), 0)
--from dbo.[vOrder]
--where OrderId in (select FillerId from dbo.Filler where OrderId = #OrderId)
--or OrderId in (select OrderId from dbo.Filler where FillerId = #OrderId)
declare #Released decimal(18, 8)
select #Released = Amount
from dbo.Trade t join dbo.JournalEntry j on t.EntryId = j.EntryId
where SourceOrderId = #OrderId
set #Released = ISNULL(#Released, 0)
print 'Order Type = ' + #OrderTypeId
print '#InitialAmount = ' + isnull(cast (#Amount as nvarchar), 'NULL') + ' ' + #AssetId
print '#AmountFilled = ' + isnull(cast (#AmountFilled as nvarchar), 'NULL') + ' ' + #AssetId
print '#AmountLeft = ' + isnull(cast (#AmountLeft as nvarchar), 'NULL')
print ''
print '#InitialBlocked = ' + isnull(cast (#Total as nvarchar), 'NULL') + ' ' + #QuoteAssetId
print '#Released = ' + cast (#Released as nvarchar) + ' ' + #QuoteAssetId
print '#CurrentBlocked = ' + isnull(cast (#TotalLeft as nvarchar), 'NULL') + ' ' + #QuoteAssetId
print ''
set #Explanation = N'Revoke order process. Reverse blocked quote amount' --+ 'reverse' --+ cast(#EntryId as nvarchar)
DECLARE #RC int
declare #Date datetimeoffset
set #Date = sysdatetimeoffset()
set #AssetReserveAccountId = (select AccountId from dbo.Account where CustomerGuid = #CustomerGuid and MasterAccountNo = 99931 and AssetId = #QuoteAssetId)
set #AssetAccountId = (select AccountId from dbo.Account where CustomerGuid = #CustomerGuid and MasterAccountNo = 9993 and AssetId = #QuoteAssetId)
set #EntryAmount = #Amount * #QuotePrice - #Released
print 'Order total = ' + cast (#Amount * #QuotePrice as nvarchar)
print 'Money spent to buy asset = ' + cast (#Released as nvarchar)
print 'Money to refund to buyer = ' + cast (#EntryAmount as nvarchar)
print '#Amount = ' + cast (#Amount as nvarchar)
print '#QuotePrice = ' + cast (#QuotePrice as nvarchar)
--rollback
--return 111
EXECUTE #RC = [dbo].[AddJournalEntry]
#Date
,#AssetReserveAccountId
,#AssetAccountId
,#EntryAmount
,#QuoteAssetId
,#Explanation
,'revoke'
,#OrderId
,#MasterEntryId
,#NewEntryId OUTPUT
if ##ERROR <> 0 or #RC <> 0
begin
rollback
raiserror ('Revoke order process. Can not add reverse blocked quote amount journal entry', 16, 1)
return 4
end
end
if (#OrderTypeId = 'sell')
begin
print 'sell order'
set #Explanation = N'Revoke order process. Reverse blocked amount journal entry' --+ 'რევერსი' --+ cast(#EntryId as nvarchar)
set #Date = sysdatetimeoffset()
set #AssetReserveAccountId = (select AccountId from dbo.Account where CustomerGuid = #CustomerGuid and MasterAccountNo = 99931 and AssetId = #AssetId)
set #AssetAccountId = (select AccountId from dbo.Account where CustomerGuid = #CustomerGuid and MasterAccountNo = 9993 and AssetId = #AssetId)
set #EntryAmount = #AmountLeft
print '#EntryAmount = ' + isnull(cast (#EntryAmount as nvarchar), 'NULL')
EXECUTE #RC = [dbo].[AddJournalEntry]
#Date
,#AssetReserveAccountId
,#AssetAccountId
,#EntryAmount
,#AssetId
,#Explanation
,'revoke'
,#OrderId
,#MasterEntryId
,#NewEntryId OUTPUT
if ##ERROR <> 0 or #RC <> 0
begin
rollback
raiserror ('Revoke order process. Can not add reverse blocked amount journal entry', 16, 1)
return 5
end
end
-- STEP 4
update dbo.[Order]
set OrderStatusId = 30
where OrderId = #OrderId
if ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback
raiserror ('Can not set order status to REVOKED', 16, 1)
return 2
end
commit tran
return 0
END
go
Custom error I am getting is defined in query called
AddJournalEntry
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE PROCEDURE [dbo].[AddJournalEntry]
#Date datetimeoffset,
#Debit int,
#Credit int,
#Amount decimal(18, 8),
#AssetId nvarchar(50),
#Explanation nvarchar(100),
#EntryType nvarchar(50),
#OrderId int = null,
#MasterEntryId int = null,
#EntryId int OUTPUT
AS
declare #DebitBalance decimal(18, 8)
declare #DebitAccountAssetId nvarchar(10)
declare #CreditAccountAssetId nvarchar(10)
declare #CreditBalance decimal(18, 8)
declare #ToIncrease nvarchar(100)
declare #DebitAccountTitle nvarchar(500)
declare #CreditAccountTitle nvarchar(500)
declare #Error nvarchar(500)
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET XACT_ABORT ON;
SET NOCOUNT ON;
print ''
print '++++++++++++++++++++++++++++++++++++++++++++++++'
print 'Start AddJournalEntry procedure'
print '++++++++++++++++++++++++++++++++++++++++++++++++'
print ''
begin tran
-- STEP 1
print '#Debit = ' + isnull(cast(#Debit as nvarchar), 'NULL')
print '#Credit = ' + isnull(cast(#Credit as nvarchar), 'NULL')
print '#AssetId = ' + cast(#AssetId as nvarchar(50))
print '#Amount = ' + cast(#Amount as nvarchar(50))
update dbo.Account
set Debit = Debit + #Amount, LastTransactionDate = SYSDATETIMEOFFSET()
where AccountId = #Debit
if ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback
raiserror ('Can not update debit account balance', 16, 1)
return 2
end
update dbo.Account
set Credit = Credit + #Amount, LastTransactionDate = SYSDATETIMEOFFSET()
where AccountId = #Credit
if ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback
raiserror ('Can not find or update credit account balance', 16, 1)
return 3
end
select
#DebitBalance = Balance,
#ToIncrease = ToIncrease,
#DebitAccountTitle = AccountFullTitle
from dbo.vAccount
where AccountId = #Debit and AssetId = #AssetId
if ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback
raiserror ('Can not find debit account', 16, 1)
return 4
end
print 'New Debit Balance = ' + cast(#DebitBalance as nvarchar(50))
if (#DebitBalance < 0 and #ToIncrease = 'debit') or (#DebitBalance > 0 and #ToIncrease = 'credit')
begin
rollback
set #Error = 'Debit account balance can not be less than 0. ' + #DebitAccountTitle
raiserror (#Error, 16, 1)
return 5
end
-- STEP 4
select
#CreditBalance = Balance,
#ToIncrease = ToIncrease,
#CreditAccountTitle = AccountFullTitle
from dbo.vAccount
where AccountId = #Credit and AssetId = #AssetId
if ##ERROR <> 0 or ##ROWCOUNT = 0
begin
rollback
raiserror ('Can not find credit account', 16, 1)
return 55
end
if (#CreditBalance > 0 and #ToIncrease = 'credit') or (#CreditBalance < 0 and #ToIncrease = 'debit')
begin
rollback
set #Error = 'Credit account balance can not be less than 0. ' + #CreditAccountTitle
raiserror ( #Error, 16, 1)
return 56
end
-- STEP 4
insert dbo.JournalEntry
select SYSDATETIMEOFFSET(), #Debit, #Credit, #Amount, #AssetId, #Explanation, #DebitBalance, #CreditBalance, #OrderId, #MasterEntryId, #EntryType, NEWID()
if ##ERROR <> 0
begin
rollback
raiserror ('Can not insert entry record', 16, 1)
return 1
end
commit tran
set #EntryId = SCOPE_IDENTITY()
return 0
END
go
You are executing multiple ROLLBACK commands when you should only execute it once. A rollback will lower the transaction count from any amount higher than 0 directly to 0, so if you execute 3 BEGIN TRANSACTION, your ##TRANCOUNT is 3 and a rollback will set it to 0. The problem is that you are executing a rollback inside the called SP (the nested one) and again after the SP returns.
You can see the problem with this example:
BEGIN TRANSACTION
SELECT ##TRANCOUNT -- 1
BEGIN TRANSACTION
SELECT ##TRANCOUNT -- 2
ROLLBACK
SELECT ##TRANCOUNT -- 0
ROLLBACK -- The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
And this is the failing execution route from your SP:
CREATE PROCEDURE [dbo].[RevokeOrder]
#OrderId int = null
AS
begin tran -- Create a transaction here (TRANCOUNT = 1)
if (...)
begin
EXECUTE #RC = [dbo].[AddJournalEntry] -- Executes a rollback inside
if ##ERROR <> 0 or #RC <> 0
begin
rollback -- When the execution reaches this rollback, TRANCOUNT is 0 and the rollback fails
raiserror ('Revoke order process. Can not add reverse blocked quote amount journal entry', 16, 1)
return 4
end
end
END
And the SP being called:
CREATE PROCEDURE [dbo].[AddJournalEntry]
AS
BEGIN
begin tran -- TRANCOUNT = 2
if (#DebitBalance < 0 and #ToIncrease = 'debit') or (#DebitBalance > 0 and #ToIncrease = 'credit')
begin
rollback -- Undoes all changes from the start of the first BEGIN TRAN and sets TRANCOUNT to 0
set #Error = 'Debit account balance can not be less than 0. ' + #DebitAccountTitle
raiserror (#Error, 16, 1)
return 5
end
END
I'd recommend using TRY/CATCH blocks and doing the ROLLBACK on the CATCH. This would be like the following:
CREATE PROCEDURE [dbo].[RevokeOrder]
#OrderId int = null
AS
BEGIN TRY
begin tran
if (...)
begin
EXECUTE #RC = [dbo].[AddJournalEntry]
if ##ERROR <> 0 or #RC <> 0
begin
raiserror ('Revoke order process. Can not add reverse blocked quote amount journal entry', 16, 1)
return 4
end
end
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 -- Might want to check XACT State also
ROLLBACK
-- Additional logging/fixing stuff
END CATCH
END
For detailed explanation on SQL Server error handling, check this post.
I have one parent stored procedure and one nested stored procedure. Parent Stored procedure calls the nested stored procedure on loop. Now when there is certain condition matches I have raised the error. When this error is raised I want to stop all the process and return the error.
CREATE Proc [dbo].[Usp_GenSalarySheet](#SalData [HRM].UTDT_SalaryData ReadOnly)
AS
set nocount on
DECLARE #SMT NVARCHAR(MAX)
SET #SMT = 'Create Table ##SalarySheet (StaffID INT,FullName NVARCHAR(1024),PresentDays NVARCHAR(1024),Absent NVARCHAR(1024),Department NVARCHAR(1024),Designation NVARCHAR(1024),'
DECLARE #HopName nvarchar(max)
Declare HOPCursor cursor for select ID from HRM.tbl_HOP
OPEN HOPCursor
FETCH NEXT FROM HOPCursor INTO #HopName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SMT = #SMT + ' [' + #HopName + '] DECIMAL(19,7),'
FETCH NEXT FROM HOPCursor into #HopName
END
SET #SMT = #SMT + '[Total] DECIMAL(19,7))'
CLOSE HOPCursor
DEALLOCATE HOPCursor
print (#smt)
exec (#SMT)
select * into #temp from #SalData
Declare #TopID INT
While (Select Count(*) From #Temp) > 0
Begin
Select Top 1 #TopID = StaffID From #temp
Declare #StaffID INT =(select top 1 StaffID from #temp)
Declare #StaffName NVARCHAR(1024) = (SELECT TOP 1 FullName FROM #temp)
Declare #WorkingDays Int = (SELECT top 1 WorkingDays from #temp)
Declare #Leave INT = (SELECT top 1 [Absent] from #temp)
INSERT INTO ##SalarySheet(StaffID,FullName,[Absent]) values(#StaffID,#StaffName,#Leave)
DECLARE #HOPType INT
DECLARE #Value Decimal(19,7)
DECLARE #CalcVal DECIMAL(19,7) = 0
DECLARE #Formula NVARCHAR(MAX)
DECLARE #Total DECIMAL(19,7)
DECLARE #PayEvery INT
DECLARE #Round Int
Declare HOPList Cursor for SELECT ID,HOPType,Value,Formula,RoundOff,PayEvery FROM HRM.Tbl_HOP order by Schedule
open HOPList
FETCH NEXT FROM HOPList INTO #HopName,#HOPType,#Value,#Formula,#Round,#PayEvery
WHILE ##FETCH_STATUS = 0
BEGIN
if exists(select * from HRM.[Tbl_ContractHOPDetails] where PersonalDetailsID = #StaffID and HOPID = #HopName)
print('select * from HRM.[Tbl_ContractHOPDetails] where PersonalDetailsID = ' + convert(varchar(max), #StaffID) + ' and HOPID =' + convert(varchar(max),#HopName))
begin
if(#HOPType=51)
begin
exec HRM.Usp_GetSalaryValueFromFormula #StaffID,#Formula,#HOPType,#Leave,#WorkingDays,#Value output
set #HOPType= 50
end
if(#HOPType=50)
begin
set #CalcVal = #value
END
IF(#HOPType=38)
BEGIN
SET #CalcVal = #Value - ((#Value/#WorkingDays) * #Leave)
END
if(#PayEvery= 40)
begin
set #CalcVal = ((#CalcVal * #WorkingDays) - (#CalcVal * #Leave))
end
if(#Round = 45)
begin
set #CalcVal = round(#CalcVal,2)
end
else if(#Round = 46)
begin
set #CalcVal = CEILING(#CalcVal)
end
else if(#Round = 47)
begin
set #CalcVal = FLOOR(#CalcVal)
end
set #SMT ='UPDATE ##SalarySheet SET [' + #HopName + '] = ' + cast(#CalcVal as nvarchar(max)) + ' where StaffID = ' + cast(#StaffID as nvarchar(max))
exec (#smt)
end
SET #CalcVal = 0
FETCH NEXT FROM HOPList INTO #HopName,#HOPType,#Value,#Formula,#Round,#PayEvery
END
close HOPList
DEALLOCATE HOPList
set #SMT ='UPDATE ##SalarySheet SET [Total] = ' + cast(#Total as nvarchar(max)) + ' where StaffID = ' + cast(#StaffID as nvarchar(max))
exec (#smt)
Delete #temp Where StaffID = #TopID
end
select * from ##SalarySheet
drop table ##SalarySheet
This is my parent Stored Procudere and nested procedure is as follow:
CREATE proc [HRM].[Usp_GetSalaryValueFromFormula](#StaffID INT,#val nvarchar(max),#HOPType INT,#Leave INT,#WorkingDays INT, #GetResult Decimal(19,7) output)
as
set nocount on
Declare #Formula Varchar(max)
declare #initial INT =0
declare #final INT =0
Declare #DataVal NVARCHAR(MAX) -- set the value from HOP table
declare #FieldVal nvarchar(max)
declare #cnt int = 0
Declare #Complete Int =CHARINDEX ('[',#val,0)
while (#Complete <> 0)
begin
set #initial = CHARINDEX ('[',#val,0)
set #final = CHARINDEX(']',#val,0)
set #FieldVal = SUBSTRING(#val,#initial,(#final-#initial) + 1)
if len(#FieldVal)<>0
begin
select #HOPType = HOPType, #DataVal= ( case when HOPType = 51 then [Formula] else cast([Value] as nvarchar(max)) end) from HRM.Tbl_ContractHOPDetails where PersonalDetailsID = #StaffID and HOPID in(select ID from HRM.tbl_HOP where HOPName = replace(replace(#fieldVal,'[',''),']',''))
if (#DataVal is null or #DataVal ='')
begin
RAISERROR ('Nested HOP is not defined.',11,1)
RETURN
end
print(#DataVal)
if ISNUMERIC(#DataVal)=1
begin
if(#HOPType = 38)
begin
SET #DataVal = cast(#DataVal as decimal(19,7)) - ((cast(#DataVal as decimal(19,7))/#WorkingDays) * #Leave)
end
end
set #val = replace(#val,#fieldVal,#DataVal)
set #fieldVal= ''
set #DataVal = ''
end
set #Complete = CHARINDEX ('[',#val,0)
set #fieldVal =''
set #final =0
set #initial = 0
end
SET #Complete =CHARINDEX ('{',#val,0)
while (#Complete <> 0)
BEGIN
set #initial = CHARINDEX ('{',#val,0)
set #final = CHARINDEX('}',#val,0)
set #FieldVal = SUBSTRING(#val,#initial+1,(#final-#initial)-1)
if len(#FieldVal)<>0
begin
set #DataVal = isnumeric((SELECT 0 FROM [HRM].Tbl_StaffTag where CValue = #FieldVal and StaffID = #StaffID))
set #FieldVal = '{' + #FieldVal + '}'
set #val = replace(#val,#fieldVal,#DataVal)
set #fieldVal= ''
set #DataVal = ''
end
set #Complete = CHARINDEX ('{',#val,0)
set #final =0
set #initial = 0
END
DECLARE #RetrunVal DECIMAL(19,7)
declare #ParmDefinition Nvarchar(512) = '#GetVal decimal(19,7) OUTPUT'
Declare #SMT NVARCHAR(MAX) = ' SET #GetVal = ' + #val
EXECUTE sp_executeSQL #Smt, #ParmDefinition, #GetVal =#RetrunVal OUTPUT
set #GetResult = #RetrunVal
But in current situation it raises error and again from the main procedure it runs next step of loop. But I want to terminate the complete process after this raiserror
Kindly help me
My guess is that RaiseError throws the control back to the caller, which might make the Return statement unreachable. The caller (loop) continues with next iteration.
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;
Hello Every One The Following Error Comes To Me Suddenly When I Changed Simple And Few Thing On My Stored Procedure Code And It Was Working Great And If I Cleared The Updated Code It Works Fine Again So I Do Not Know What Is The Reason
And Here Is The Error "The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction"
Thanks In Advance
ALTER Procedure [dbo].[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]
#English Bit = 0 ,
#ID BigInt = NULL Output ,
#Company_ID SmallInt = NULL ,
#Cust_ID NVarChar (20) = NULL ,
#Cust_Cat NVarChar (10) = NULL ,
#Debit_Limit Decimal (18,3) = NULL ,
#From_Date NVarChar (19) = NULL ,
#To_Date NVarChar (19) = NULL ,
#User_ID NVarChar(50) = NULL
As
BEGIN
Create Table #Errors (ErrorNumber int Not Null, ErrorValue nvarchar(300) Collate Arabic_CI_AS Null);
Begin Tran trn_INV_CustDebLim_setupInsert;
Begin Try
Insert Into INV_Cust_Debit_Limit_setup
( Company_ID , Cust_ID , Cust_Cat , Debit_Limit , From_Date , To_Date )
Values
( #Company_ID , #Cust_ID , #Cust_Cat , #Debit_Limit , #From_Date , #To_Date )
set #ID = ##IDENTITY
-- From Here Is New Part --
declare #str nvarchar(50)
IF(#Cust_ID IS NOT NULL)
set #str = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx ' + #Cust_ID
IF(#Cust_Cat IS NOT NULL)
set #str += 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx ' + #Cust_Cat
set #str += ' xxxxx' +#Debit_Limit + ' xxxx xx' +#From_Date
IF(#English = 1)
BEGIN
IF(#Cust_ID IS NOT NULL)
set #str = 'Credit Limit Added On Customer ' + #Cust_ID
IF(#Cust_Cat IS NOT NULL)
set #str += 'Credit Limit Added On Customer Category ' + #Cust_Cat
set #str = ' With Value ' +#Debit_Limit + ' From Date ' +#From_Date
END
exec usp_GLApplicationAudit #Company_ID , NULL , 10 , #User_ID , #str ,#ID ,10, NULL , NULL ,NULL ,NULL,NULL,'SAl'
-- To Here Is New Part --
End Try
Begin Catch
Insert #Errors Values(1002,ERROR_MESSAGE());
Goto Finished;
End Catch
-- Return Error Records
Finished:
-- retrieve Errors and commit/Rollbak Trans.
If (Select count(E.ErrorNumber)
From #Errors E Left Join GVT_Errors G
On E.ErrorNumber = G.Err_Number
Where G.Err_Type=0 ) > 0
Begin
-- The Following are Errors
Select E.ErrorNumber As [Err_No], G.MessageA + ': ' + E.ErrorValue As [Err_Desc], Err_Source, dbo.fn_GetItemDescription('Err_Type',Cast(Err_Type As nvarchar), null, null, null, null, 0) As Err_Type_Desc, Err_Type, SeverityLevel As [Severity], CategoryID
From #Errors E Left Join GVT_Errors G
On E.ErrorNumber = G.Err_Number;
Rollback Tran trn_INV_CustDebLim_setupInsert;
End
Else
Begin
-- The Following Not Errors They are Warnings or Information
Select E.ErrorNumber As [Err_No], G.MessageA + ': ' + E.ErrorValue As [Err_Desc], Err_Source, dbo.fn_GetItemDescription('Err_Type',Cast(Err_Type As nvarchar), null, null, null, null, 0) As Err_Type_Desc, Err_Type, SeverityLevel As [Severity], CategoryID
From #Errors E Left Join GVT_Errors G
On E.ErrorNumber = G.Err_Number;
Commit Tran trn_INV_CustDebLim_setupInsert;
End
DROP Table #Errors;
END
In the CATCH code you must check the state of XACT_STATE() and act accordingly. For a procedure template that handles transactions and try/catch blocks correctly see Exception Handling and Nested Transactions:
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare #trancount int;
set #trancount = ##trancount;
begin try
if #trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
lbexit:
if #trancount = 0
commit;
end try
begin catch
declare #error int, #message varchar(4000), #xstate int;
select #error = ERROR_NUMBER(), #message = ERROR_MESSAGE(), #xstate = XACT_STATE();
if #xstate = -1
rollback;
if #xstate = 1 and #trancount = 0
rollback
if #xstate = 1 and #trancount > 0
rollback transaction usp_my_procedure_name;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
end catch
end