SQL - Transaction count after EXECUTE indicates a mismatch - sql

I am working on a stored procedure in MS SQL Server Management Studio 2012.
I am reusing some code from other procedures and this includes calling another stored procedure with the EXEC command.
This procedure is going to take information for an object as parameters, the reset that object's status, and then look for related (downstream) objects and reset their statuses as well.
The code was working but required a small change to reset and related object's status even if it was not in an error status. To address this issue, I created a variable before the main loop that iterates over the related objects, and then when the first object that has an error status is encountered, it sets the value of the variable. The variable itself is a BIT.
Here is the procedure:
USE [DB_TEST]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROC [dbo].[resetTasksInErrorStatus]
#TaskId INT,
#SubjectId INT,
#ProjectProtocolId INT,
#TimePointId INT,
#WorkbookId INT,
#LogCreatorUserId INT,
#LogReasonTypeId INT,
#LogOtherReason VARCHAR(256),
#SignatureCaptured BIT,
#ManualChangeTaskStatus BIT,
#ErrorIfModifiedAfter DATETIME,
#NewTaskStatusID INT,
#ProcessorUserId INT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION ResetTasksInError
-- Make sure the task has not been modified since the reset was requested
IF(EXISTS(
SELECT ti.* FROM TaskInstance as ti
WHERE ti.ModifyDate > #ErrorIfModifiedAfter
AND ti.TaskId = #TaskId
AND ti.SubjectId = #SubjectId
AND ti.ProjectProtocolId = #ProjectProtocolId
AND ti.TimePointId = #TimePointId
AND ti.WorkbookId = #WorkbookId))
BEGIN
RAISERROR('The task to be reset was modified before the reset could complete', 16, 1);
GOTO error
END
-- Get all downstream task instances
SELECT *
INTO #downstreamTaskInstances
from dbo.fnGetTaskInstancesDownstream
(
#TaskId,
#SubjectId,
#TimePointId
)
-- Get the previous task status
DECLARE #OldTaskStatus INT;
SELECT TOP 1 #OldTaskStatus = ti.TaskStatusTypeId FROM TaskInstance as ti
WHERE ti.TaskId = #TaskId
AND ti.SubjectId = #SubjectId
AND ti.TimePointId = #TimePointId
-- Reset the task
EXEC setTaskStatus
#TaskID,
#SubjectId,
#ProjectProtocolId,
#TimePointId,
#WorkBookId,
#OldTaskStatus,
#NewTaskStatusID,
#ProcessorUserId,
#LogCreatorUserId,
#LogReasonTypeId,
#LogOtherReason,
#SignatureCaptured,
#ManualChangeTaskStatus,
NULL,
NULL,
NULL,
1
-- Check if setTaskStatus rolled back our transaction
IF(##TRANCOUNT = 0)
BEGIN
RAISERROR('Error in sub procedure. Changes rolled back.', 16, 1);
RETURN
END
-- Set a boolean variable to determine whether downstream tasks should be reset
DECLARE #ResetDownstreamTasks BIT = 0;
--Set #ResetDownstreamTasks = 0;
-- Create a cursor of the downstream tasks
DECLARE downstreamCursor CURSOR FOR
SELECT TaskId, TimePointId, ProcessorUserId, TaskStatus, WorkBookId, ProjectProtocolId, IsManual, TaskStatus
FROM #downstreamTaskInstances
OPEN downstreamCursor
-- Reset each downstream task to unprocessed
DECLARE #CursorTaskID INT;
DECLARE #CursorTimePointID INT;
DECLARE #CursorProcessorUserId INT;
DECLARE #CursorTaskStatusID INT;
DECLARE #CursorWorkBookId INT;
DECLARE #CursorProjectProtocolId INT;
DECLARE #CursorIsManual BIT;
Declare #CursorTaskStatus INT;
FETCH NEXT FROM downstreamCursor INTO #CursorTaskID, #CursorTimePointId, #CursorProcessorUserId, #CursorTaskStatusID, #CursorWorkBookId, #CursorProjectProtocolId, #CursorIsManual, #CursorTaskStatus
WHILE ##FETCH_STATUS = 0 AND ##ERROR = 0 AND ##TRANCOUNT = 1
BEGIN
-- Check if the task is in error status, and then make sure it is not an manual task.
-- Manual tasks should never be in error status, so there is no need to reset them.
if #CursorTaskStatus = 10 and #CursorIsManual <> 1
begin
SET #ResetDownstreamTasks = 1;
EXEC setTaskStatus
#CursorTaskID,
#SubjectId,
#CursorProjectProtocolId,
#CursorTimePointId,
#CursorWorkBookId,
#CursorTaskStatusID,
1, -- Unprocessed
#CursorProcessorUserId,
#LogCreatorUserId,
#LogReasonTypeId,
#LogOtherReason,
#SignatureCaptured,
#ManualChangeTaskStatus,
NULL,
NULL,
NULL,
0
end;
if #ResetDownstreamTasks = 1
begin
EXEC setTaskStatus
#CursorTaskID,
#SubjectId,
#CursorProjectProtocolId,
#CursorTimePointId,
#CursorWorkBookId,
#CursorTaskStatusID,
6, -- Inspected
#CursorProcessorUserId,
#LogCreatorUserId,
#LogReasonTypeId,
#LogOtherReason,
#SignatureCaptured,
#ManualChangeTaskStatus,
NULL,
NULL,
NULL,
0
end
FETCH NEXT FROM downstreamCursor INTO #CursorTaskID, #CursorTimePointId, #CursorProcessorUserId, #CursorTaskStatusID, #CursorWorkBookId, #CursorProjectProtocolId, #CursorIsManual, #CursorTaskStatus
END
DROP TABLE #downstreamTaskInstances
CLOSE downstreamCursor
DEALLOCATE downstreamCursor
-- Check if setTaskStatus rolled back our transaction
IF(##TRANCOUNT = 0)
BEGIN
RAISERROR('Error in sub procedure. Changes rolled back.', 16, 1);
RETURN
END
IF(##ERROR <> 0)
BEGIN
GOTO ERROR
END
COMMIT TRANSACTION ResetTasksInError
RETURN
ERROR:
RAISERROR('Error encountered. Changes rolled back.', 16,1);
ROLLBACK TRANSACTION ResetTasksInError
RETURN
END
When I run the procedure I get these errors:
Msg 266, Level 16, State 2, Procedure setTaskStatus, Line 0
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
Msg 16943, Level 16, State 4, Procedure resetTasksInErrorStatus, Line 197
Could not complete cursor operation because the table schema changed after the cursor was declared.
Msg 3701, Level 11, State 5, Procedure resetTasksInErrorStatus, Line 200
Cannot drop the table '#downstreamTaskInstances', because it does not exist or you do not have permission.
Msg 50000, Level 16, State 1, Procedure resetTasksInErrorStatus, Line 207
Error in sub procedure. Changes rolled back.
If I comment out the SET ... statement the procedure runs and works (not as desired).
I have looked around for similar questions, but none of them solved my problem.
Is there something I am missing with regards to the SET statement?
Is it affecting the ##TRANCOUNT variable somehow?
I did see some posts around that mentioned the problem is likely to be in the stored procedure that this one calls, but I am a little hesitant because that stored procedure works, and these errors only show up when trying to set the value of the variable.

Just a guess .. but i think that what happens is the following:
you create a temp table inside the transaction.
you start a cursor and iterate through it until one of the inner sps gives error.
the transaction is rolledback so the temp table will go with the transaction
the cursor gives the schema change error
drop error
So you should check for the error in one of the inner sps.
Hope it helps.

I ended finding a way to make the stored procedure work.
What I did was I removed the stored procedure EXEC statement from the if statement that set the value of the variable.
So the code in the loop looks like this:
WHILE ##FETCH_STATUS = 0 AND ##ERROR = 0 AND ##TRANCOUNT = 1
BEGIN
-- Check if the task is in error status, and then make sure it is not an manual task.
-- Manual tasks should never be in error status, so there is no need to reset them.
if #CursorTaskStatus = 10 and #CursorIsManual <> 1
begin
SET #ResetDownstreamTasks = 1;
end;
if #ResetDownstreamTasks = 1
begin
EXEC setTaskStatus
#CursorTaskID,
#SubjectId,
#CursorProjectProtocolId,
#CursorTimePointId,
#CursorWorkBookId,
#CursorTaskStatusID,
6, -- Inspected
#CursorProcessorUserId,
#LogCreatorUserId,
#LogReasonTypeId,
#LogOtherReason,
#SignatureCaptured,
#ManualChangeTaskStatus,
NULL,
NULL,
NULL,
0
end
FETCH NEXT FROM downstreamCursor INTO #CursorTaskID, #CursorTimePointId, #CursorProcessorUserId, #CursorTaskStatusID, #CursorWorkBookId, #CursorProjectProtocolId, #CursorIsManual, #CursorTaskStatus
END

Related

Service Broker queue "is currently disabled" several times right after upgrading from SQL Server 2012 to 2017, nothing in logs

Yesterday we took our SQL Server 2012 instance which has been processing messages for several years without any issues (except a new periodic performance issue that started several months ago, details below), and upgraded it from SQL Server 2012 to SQL Server 2017+CU29 (KB5010786). We have tried moving compat from 2012 to 2017 and while it helps with a new issues we're seeing, performance is not great.
But the big thing is: since then, we've had the queue spontaneously disable itself twice. It works for minutes/hours, then poof the queue is disabled. We have logging and nothing's giving us anything. We had briefly turned on Query Store, but after the queue disabled the first time, we went looking for similar issue. We found a thread online that said they were having similar problems and that it was Query Store, so we immediately flipped it to Read-Only (we had turned it on in order to try and fix a performance issue we see on Mondays, where it grabs a bad plan and rebooting it seems to be the only fix, but we don't see that the rest of the week). Also of note, this doesn't update rows, it just inserts new rows into a series of hourly tables.
We're also seeing massive locking on LCK_M_IX on where we didn't before. Looking at that next. It's on a part of the code that does an insert into a table, where the output is generated from a CLR. (INSERT INTO table FROM SELECT clr). Moving from 2012 to 2017 seems to have changed that behavior, but it's still seems like it's slow overall, but I'm terrified about it spontaneously disabling again.
We are running the same load on two separate servers, so I have the ability to compare things.
The "disabled" message in our logging table appears several times all at the same time (I'm guessing once per thread). Nothing in the SQL Error Log. Interestingly, in some of the rows in the logging table, the message_body is NULL, but has a body in others. But we see no errors for several minutes before it occurred in either.
The service queue "ODS_TargetQueue" is currently disabled.
We're also running a Extended Event that logs any severity 11+ errors.
All it's showing is
The service queue "ODS_TargetQueue" is currently disabled.
We are also seeing this sporadically which we normally don't see unless we're having log backup issues:
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
We have also seen this a handful of times this morning, which seems to be new:
Process ID 57 attempted to unlock a resource it does not own: METADATA: database_id = 7 CONVERSATION_ENDPOINT_RECV($hash = 0x9904a343:0x9c8327f9:0x4b), lockPartitionId = 0. Retry the transaction, because this error may be caused by a timing condition. If the problem persists, contact the database administrator.
The queue:
CREATE QUEUE [svcBroker].[ODS_TargetQueue] WITH STATUS = ON , RETENTION = OFF , ACTIVATION ( STATUS = ON , PROCEDURE_NAME = [svcBroker].[ODS_TargetQueue_Receive] , MAX_QUEUE_READERS = 30 , EXECUTE AS OWNER ), POISON_MESSAGE_HANDLING (STATUS = ON) ON [PRIMARY]
GO
The procedure
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE PROCEDURE [svcBroker].[ODS_TargetQueue_Receive]
AS
BEGIN
set nocount on
-- Variable table for received messages.
DECLARE #receive_table TABLE(
queuing_order BIGINT,
conversation_handle UNIQUEIDENTIFIER,
message_type_name SYSNAME,
message_body xml);
-- Cursor for received message table.
DECLARE message_cursor CURSOR LOCAL FORWARD_ONLY READ_ONLY
FOR SELECT
conversation_handle,
message_type_name,
message_body
FROM #receive_table ORDER BY queuing_order;
DECLARE #conversation_handle UNIQUEIDENTIFIER;
DECLARE #message_type SYSNAME;
DECLARE #message_body xml;
-- Error variables.
DECLARE #error_number INT;
DECLARE #error_message VARCHAR(4000);
DECLARE #error_severity INT;
DECLARE #error_state INT;
DECLARE #error_procedure SYSNAME;
DECLARE #error_line INT;
DECLARE #error_dialog VARCHAR(50);
BEGIN TRY
WHILE (1 = 1)
BEGIN
BEGIN TRANSACTION;
-- Receive all available messages into the table.
-- Wait 5 seconds for messages.
WAITFOR (
RECEIVE TOP (1000)
[queuing_order],
[conversation_handle],
[message_type_name],
convert(xml, [message_body])
FROM svcBroker.ODS_TargetQueue
INTO #receive_table
), TIMEOUT 2000;
IF ##ROWCOUNT = 0
BEGIN
COMMIT;
BREAK;
END
ELSE
BEGIN
OPEN message_cursor;
WHILE (1=1)
BEGIN
FETCH NEXT FROM message_cursor
INTO #conversation_handle,
#message_type,
#message_body;
IF (##FETCH_STATUS != 0) BREAK;
-- Process a message.
-- If an exception occurs, catch and attempt to recover.
BEGIN TRY
IF #message_type = 'svcBroker_ods_claim_request'
BEGIN
exec ParseRequestMessages #message_body
END
ELSE IF #message_type in ('svcBroker_EndOfStream', 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
BEGIN
-- initiator is signaling end of message stream: end the dialog
END CONVERSATION #conversation_handle;
END
ELSE IF #message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
BEGIN
-- If the message_type indicates that the message is an error,
-- raise the error and end the conversation.
WITH XMLNAMESPACES ('http://schemas.microsoft.com/SQL/ServiceBroker/Error' AS ssb)
SELECT
#error_number = CAST(#message_body AS XML).value('(//ssb:Error/ssb:Code)[1]', 'INT'),
#error_message = CAST(#message_body AS XML).value('(//ssb:Error/ssb:Description)[1]', 'VARCHAR(4000)');
SET #error_dialog = CAST(#conversation_handle AS VARCHAR(50));
RAISERROR('Error in dialog %s: %s (%i)', 16, 1, #error_dialog, #error_message, #error_number);
END CONVERSATION #conversation_handle;
END
END TRY
BEGIN CATCH
SET #error_number = ERROR_NUMBER();
SET #error_message = ERROR_MESSAGE();
SET #error_severity = ERROR_SEVERITY();
SET #error_state = ERROR_STATE();
SET #error_procedure = ERROR_PROCEDURE();
SET #error_line = ERROR_LINE();
IF XACT_STATE() = -1
BEGIN
-- The transaction is doomed. Only rollback possible.
-- This could disable the queue if done 5 times consecutively!
ROLLBACK TRANSACTION;
-- Record the error.
BEGIN TRANSACTION;
INSERT INTO svcBroker.target_processing_errors (
error_conversation,[error_number],[error_message],[error_severity],
[error_state],[error_procedure],[error_line],[doomed_transaction],
[message_body])
VALUES (NULL, #error_number, #error_message,#error_severity,
#error_state, #error_procedure, #error_line, 1, #message_body);
COMMIT;
-- For this level of error, it is best to exit the proc
-- and give the queue monitor control.
-- Breaking to the outer catch will accomplish this.
RAISERROR ('Message processing error', 16, 1);
END
ELSE IF XACT_STATE() = 1
BEGIN
-- Record error and continue processing messages.
-- Failing message could also be put aside for later processing here.
-- Otherwise it will be discarded.
INSERT INTO svcBroker.target_processing_errors (
error_conversation,[error_number],[error_message],[error_severity],
[error_state],[error_procedure],[error_line],[doomed_transaction],
[message_body])
VALUES (NULL, #error_number, #error_message,#error_severity,
#error_state, #error_procedure, #error_line, 0, #message_body);
END
END CATCH
END
CLOSE message_cursor;
DELETE #receive_table;
END
COMMIT;
END
END TRY
BEGIN CATCH
-- Process the error and exit the proc to give the queue monitor control
SET #error_number = ERROR_NUMBER();
SET #error_message = ERROR_MESSAGE();
SET #error_severity = ERROR_SEVERITY();
SET #error_state = ERROR_STATE();
SET #error_procedure = ERROR_PROCEDURE();
SET #error_line = ERROR_LINE();
IF XACT_STATE() = -1
BEGIN
-- The transaction is doomed. Only rollback possible.
-- This could disable the queue if done 5 times consecutively!
ROLLBACK TRANSACTION;
-- Record the error.
BEGIN TRANSACTION;
INSERT INTO svcBroker.target_processing_errors (
error_conversation,[error_number],[error_message],[error_severity],
[error_state],[error_procedure],[error_line],[doomed_transaction],
[message_body])
VALUES(NULL, #error_number, #error_message,#error_severity, #error_state, #error_procedure, #error_line, 1, #message_body);
COMMIT;
END
ELSE IF XACT_STATE() = 1
BEGIN
-- Record error and commit transaction.
-- Here you could also save anything else you want before exiting.
INSERT INTO svcBroker.target_processing_errors (
error_conversation,[error_number],[error_message],[error_severity],
[error_state],[error_procedure],[error_line],[doomed_transaction],
[message_body])
VALUES(NULL, #error_number, #error_message, #error_severity, #error_state, #error_procedure, #error_line, 0, #message_body);
COMMIT;
END
END CATCH
END;
GO

Managing transaction in stored procedure with Dynamic SQL

I have a stored procedure with Dynamic SQL. Is it possible to include a batch of dynamic SQL inside an explicit transaction with COMMIT or ROLLBACK depending on the value of ##ERROR?
Following similar stored procedure. It is simplified in order to demonstration purpose.
CREATE PROCEDURE [dbo].[sp_Example]
AS
BEGIN
BEGIN TRANSACTION
DECLARE #ID VARCHAR(10)
INSERT INTO [dbo].[Deparment] (Name,Location,PhoneNumber) VALUES ('DeparmentName','DeparmentLocation','0112232332')
SELECT #ID =SCOPE_IDENTITY()
IF ##ERROR <> 0
BEGIN
ROLLBACK
RAISERROR ('Error in Inserting Deparment.', 16, 1)
RETURN
END
SET #InsertQuery = '
DECLARE #Name varchar(100)
SELECT #Name = Name
FROM dbo.[Deparment]
WHERE DepartmentId= ''' + #ID +'''
INSERT INTO [dbo].[Employee](Name,Age,Salary,DepartmentName)VALUES(''EMPLOYEE NAME'',''25'',''200000'','''+#NAME'')''
EXEC(#InsertQuery)
IF ##ERROR <> 0
BEGIN
ROLLBACK
RAISERROR ('Error in Inserting Employee.', 16, 1)
RETURN
END
COMMIT
END
Does outer Transaction scope applies to Dynamic query ?
The "outer" transaction will apply to everything that is executed. There is no way in SQL Server to not execute under the running transaction (which can be annoying of you want to log errors).

How to log errors even if the transaction is rolled back?

Lets say we have following commands:
SET XACT_ABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF
DECLARE #index int
SET #index = 4;
DECLARE #errorCount int
SET #errorCount = 0;
BEGIN TRANSACTION
WHILE #index > 0
BEGIN
SAVE TRANSACTION Foo;
BEGIN TRY
-- commands to execute...
INSERT INTO AppDb.dbo.Customers VALUES('Jalal', '1990-03-02');
-- make a problem
IF #index = 3
INSERT INTO AppDb.dbo.Customers VALUES('Jalal', '9999-99-99');
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION Foo; -- I want to keep track of previous logs but not works! :(
INSERT INTO AppDb.dbo.LogScripts VALUES(NULL, 'error', 'Customers', suser_name());
SET #errorCount = #errorCount + 1;
END CATCH
SET #index = #index - 1;
END
IF #errorCount > 0
ROLLBACK TRANSACTION
ELSE
COMMIT TRANSACTION
I want to execute a batch, keep all errors in log and then, if no error was occurred, commit all changes. How can implement it in Sql Server?
The transaction is tied to the connection, and as such, all writes will be rolled back on the outer ROLLBACK TRANSACTION (irrespective of the nested savepoints).
What you can do is log the errors to an in-memory structure, like a Table Variable, and then, after committing / rolling back the outer transaction, you can then insert the logs collected.
I've simplified your Logs and Customers tables for the purpose of brevity:
CREATE TABLE [dbo].[Logs](
[Description] [nvarchar](max) NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[Customers](
[ID] [int] NOT NULL,
[Name] [nvarchar](50) NULL
);
GO
And then you can track the logs in the table variable:
SET XACT_ABORT OFF;
SET IMPLICIT_TRANSACTIONS OFF
GO
DECLARE #index int;
SET #index = 4;
DECLARE #errorCount int
SET #errorCount = 0;
-- In memory storage to accumulate logs, outside of the transaction
DECLARE #TempLogs AS TABLE (Description NVARCHAR(MAX));
BEGIN TRANSACTION
WHILE #index > 0
BEGIN
-- SAVE TRANSACTION Foo; As per commentary below, savepoint is futile here
BEGIN TRY
-- commands to execute...
INSERT INTO Customers VALUES(1, 'Jalal');
-- make a problem
IF #index = 3
INSERT INTO Customers VALUES(NULL, 'Broken');
END TRY
BEGIN CATCH
-- ROLLBACK TRANSACTION Foo; -- Would roll back to the savepoint
INSERT INTO #TempLogs(Description)
VALUES ('Something bad happened on index ' + CAST(#index AS VARCHAR(50)));
SET #errorCount = #errorCount + 1;
END CATCH
SET #index = #index - 1;
END
IF #errorCount > 0
ROLLBACK TRANSACTION
ELSE
COMMIT TRANSACTION
-- Finally, do the actual insertion of logs, outside the boundaries of the transaction.
INSERT INTO dbo.Logs(Description)
SELECT Description FROM #TempLogs;
One thing to note is that this is quite an expensive way to process data (i.e. attempt to insert all data, and then roll back a batch if there were any problems encountered). An alternative here would be to validate all the data (and return and report errors) before attempting to insert any data.
Also, in the example above, the Savepoint serves no real purpose, as even 'successful' Customer inserts will be eventually rolled back if any errors were detected for the batch.
SqlFiddle here - The loop is completed, and despite 3 customers being inserted, the ROLLBACK TRANSACTION removes all successfully inserted customers. However, the log is still written, as the Table Variable is not subjected to the outer transaction.

T-SQL - Incorporating Try-Catch and Error-Handling (Transactions)

I am working on a Transaction using Repeatable Read Isolation Level.
I want to incorporate both Try-Catch and an Error-handler features in this Transaction.
When I run the code, I get an error message that :
Msg 102, Level 15, State 1, Line 18
Incorrect syntax near 'BEGIN'.
Msg 102, Level 15, State 1, Line 23
Incorrect syntax near '#errnum'.
How do I complete this Transaction successfully? OR
What is the correct way to write this Transaction?
This is my work as at now:
CREATE PROCEDURE ItemFlow (#Name VARCHAR(50),
#aPrice MONEY,
#bPrice MONEY)
AS
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
GO
BEGIN TRAN
IF EXISTS (SELECT 1
FROM Cashier
WHERE Item = '#Item')
UPDATE Cashier
SET bPrice = '#bPrice',
aprice = '#aprice'
WHERE Item = '#Item'
ELSE
INSERT INTO Cashier
(Item, aPrice, bPrice)
VALUES ('#Item', '#aPrice', '#bPrice')
END
BEGIN TRY
EXECUTE ItemFlow
END TRY
BEGIN CATCH
#errnum = ERROR_NUMBER(),
#severity = ERROR_SEVERITY(),
#errstate = ERROR_STATE(),
#proc = ERROR_PROCEDURE(),
#line = ERROR_LINE(),
#message = ERROR_MESSAGE()
END CATCH
One problem is the GO that terminates the create procedure statement. I always use begin/end with stored procedures:
CREATE PROCEDURE ItemFlow (#Name VARCHAR(50),
#aPrice MONEY,
#bPrice MONEY)
AS
BEGIN
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRAN
IF EXISTS (SELECT 1
FROM Cashier
WHERE Item = '#Item')
UPDATE Cashier
SET bPrice = '#bPrice',
aprice = '#aprice'
WHERE Item = '#Item'
ELSE
INSERT INTO Cashier
(Item, aPrice, bPrice)
VALUES ('#Item', '#aPrice', '#bPrice')
COMMIT TRAN;
END; -- ItemFlow
Then, you need arguments when you call the stored procedure, or default values defined for them:
BEGIN TRY
EXECUTE ItemFlow #arg1, #arg2, #arg3
END TRY
BEGIN CATCH
#errnum = ERROR_NUMBER(),
#severity = ERROR_SEVERITY(),
#errstate = ERROR_STATE(),
#proc = ERROR_PROCEDURE(),
#line = ERROR_LINE(),
#message = ERROR_MESSAGE()
END CATCH;
Before implementing any of the suggested changes, please answer: 1) why you are calling the proc itself within the proc? and 2) why are you setting the ERROR_ functions into variables? Are you going to use them? If not, no need to declare variables.
Also:
Obviously the GO can't be a part of this. It is just a batch separator for SSMS.
You have an input param called #Name that is not used, and a variable called #Item that is not declared. I suspect they are the same thing so I have used #Item as the input param instead of #Name.
You use the input params as string literals in the three queries, which makes no sense. You need to remove the single-quotes from around them so that they can act as variables.
And, please do NOT separate the TRY / CATCH logic from the transaction!
Assuming that there was no real intention in calling an infinite loop (the proc calling itself with no condition to ever stop), your code should look as follows:
CREATE PROCEDURE ItemFlow
(
#Item VARCHAR(50),
#aPrice MONEY,
#bPrice MONEY
)
AS
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRY
BEGIN TRAN
IF EXISTS (SELECT 1
FROM Cashier
WHERE Item = #Item)
BEGIN
UPDATE Cashier
SET bPrice = #bPrice,
aprice = #aPrice
WHERE Item = #Item;
END;
ELSE
BEGIN
INSERT INTO Cashier
(Item, aPrice, bPrice)
VALUES (#Item, #aPrice, #bPrice);
END;
COMMIT TRAN;
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRAN;
END;
THROW;
END CATCH;
THROW was introduced in SQL Server 2012. If you are using anything from 2005 - 2008 R2, replace the THROW with:
DECLARE #ErrMessage NVARCHAR(4000);
SET #ErrMessage = ERROR_MESSAGE();
RAISERROR(#ErrMessage, 16, 1);
RETURN;
Your code has a few problems:
Error near BEGIN
GO is a batch separator. It's not valid T-SQL and only understood by SSMS. You are effectively submitting two queries:
CREATE PROCEDURE ItemFlow (#Name VARCHAR(50),
#aPrice MONEY,
#bPrice MONEY)
AS
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
and:
BEGIN TRAN
...
As you can see, the stored procedure body is empty. Remove GO to get rid of this.
Error near #errnum
Your variables are not declared. Also, you must use SELECT or SET to assign a value to these variables:
DECLARE #errnum int,
#serverity int,
(etc.)
SELECT #errnum = ERROR_NUMBER(),
#severity = ERROR_SEVERITY(),
#errstate = ERROR_STATE(),
#proc = ERROR_PROCEDURE(),
#line = ERROR_LINE(),
#message = ERROR_MESSAGE()
One last thing:
You have a BEGIN TRAN but not a COMMIT TRAN. Your transaction is still open by the end of the sproc's execution.

The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION

What is the problem with this code.
It is giving this error The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION. when some exception is coming in the procedure. How can I solve it?
BEGIN
BEGIN TRANSACTION
DECLARE
#Id bigint
,#Month nvarchar(100)
,#Year nvarchar(100)
,#CountryofExport nvarchar(100)
,#CountryofOrigin nvarchar(100)
,#HSCode nvarchar(100)
,#Unit nvarchar(100)
,#Quantity nvarchar(100)
,#CustomValue nvarchar(255)
,#Type nvarchar(100)
,#TypeBit bit
,#CountryofExportID int
,#CountryofOriginID int
,#MeasurementId int
,#Remarks nvarchar(500)
,#CommodityId int
,#SDate nvarchar(100)
,#SameRec int
,#counts int
DECLARE #Cursor_TradeFlow CURSOR
SET #Cursor_TradeFlow = CURSOR FOR
SELECT [Id],[Months],[Years],[CountryofExport],[CountryofOrigin],[HSCode],[Quantity],[Unit],[CustomValue],[Type] FROM [Temp_Trading]
OPEN #Cursor_TradeFlow
FETCH NEXT FROM #Cursor_TradeFlow INTO #Id, #Month, #Year, #CountryofExport, #CountryofOrigin, #HSCode,#Quantity, #Unit, #CustomValue, #Type
WHILE ##FETCH_STATUS = 0
BEGIN
Set #Remarks='';
Declare #EICountry varchar(100),
#Checkbit bit,
#CheckYearIsNumeric bit,
#CheckMonthIsNumeric bit
BEGIN TRY
SET #CheckMonthIsNumeric= convert(INT, #Month);
END TRY
BEGIN CATCH
begin
set #Checkbit=1;
set #Remarks = #Remarks + 'Invalid Month'
set #CheckMonthIsNumeric=1
end
END CATCH
BEGIN TRY
set #CheckYearIsNumeric= convert(INT, #Year);
END TRY
BEGIN CATCH
SET #CheckYearIsNumeric= 1;
set #Checkbit=1;
set #Remarks = #Remarks + 'Invalid Year'
END CATCH
Set #SameRec = (Select COUNT(*) From TradeFlow Where int_Month = #CheckMonthIsNumeric and int_Year = #CheckYearIsNumeric
and int_OriginLocationId = #CountryofExportID and int_DestinationLocationId = #CountryofOriginID and int_CommodityId = #CommodityId
and int_MeasurementId = #MeasurementId)
IF ##ERROR <> 0
BEGIN
ROLLBACK
END
FETCH NEXT FROM #Cursor_TradeFlow INTO #Id, #Month, #Year, #CountryofExport, #CountryofOrigin, #HSCode,#Quantity, #Unit, #CustomValue, #Type
END
CLOSE #Cursor_TradeFlow
DEALLOCATE #Cursor_TradeFlow
COMMIT
END
Having:
IF ##ERROR <> 0
BEGIN
ROLLBACK
END
inside a cursor loop is a bad sign - you rollback the transaction, and then continue into the next iteration. When the loop finally finishes, you attempt to commit and - Oops - there's no open transaction any longer, and every operation after the rollback has been left in place.
You might want to exit the loop after the rollback using a GOTO, or deal with the errors in a different way. It's too hard to tell what the best strategy might be.
You could use named transactions:
-- big transaction in the beginning
BEGIN TRANSACTION BIG_TRANSACTION
-- your code here
-- a transaction for each fetched item
BEGIN TRANSACTION FETCH_TRANSACTION
-- your code here
if OK
COMMIT TRANSACTION FETCH_TRANSACTION
else
ROLLBACK TRANSACTION FETCH_TRANSACTION
COMMIT/ROLLBACK TRANSACTION BIG_TRANSACTION