tSQLt There was also a ROLLBACK ERROR in Private_RunTest - sql-server

Whenever I use ExpectException I receive the following error: (There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.{Private_RunTest,140})
It appears that tSQLt is attempting a ROLLBACK after the fact that MSSQL has already performed the ROLLBACK due to the RAISERROR.
I wrapped the following SELECT and SET statement in Private_RunTest with the following IF statement and it appeared to resolve the problem.
IF ISNULL(#ExpectException,0) <> 1
BEGIN
SELECT #Msg = COALESCE(#Msg, '') + ' (There was also a ROLLBACK ERROR --> ' +
COALESCE(ERROR_MESSAGE(), '') + '{' +
COALESCE(ERROR_PROCEDURE(), '') + ',' +
COALESCE(CAST(ERROR_LINE() AS NVARCHAR), '') + '})';
SET #Result = 'Error';
END
Is this truly a bug and/or an appropriate fix?

You are probably not using TRY/CATCH blocks in your tsqlt test.
The error message "There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction." can be reproduced as follows:
1) Create a Trigger that throws an error and rolls back as follows:
CREATE TRIGGER [dbo].[MyTable_IUDTR] ON [dbo].[MyTable]
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
BEGIN TRY
RAISERROR('MyError', 16, 1)
END TRY
BEGIN CATCH
THROW;
END CATCH
END
GO
2) Create a tsqlt test to check the error message returned:
CREATE PROC [ut_MyTable_IUDTR].[test that the error is returned]
AS
BEGIN
DECLARE #ErrorMsg VARCHAR(50)
EXEC tsqlt.FakeTable #TableName = 'MyTable'
EXEC tsqlt.ApplyTrigger 'MyTable', 'MyTable_IUDTR'
INSERT INTO dbo.MyTable
( FirstName, LastName )
VALUES ( N'John',N'Smith')
SET #ErrorMsg = ERROR_MESSAGE()
EXEC tSQLt.AssertEqualsString #Expected = 'MyError', #Actual = #ErrorMsg
END
GO
3) Run the test:
EXEC [tSQLt].Run 'ut_MyTable_IUDTR.test that the error is returned'
4) You get the following Error:
There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.
THE FIX:
Change the tsqlt test to include TRY/CATCH blocks as follows:
ALTER PROC [ut_MyTable_IUDTR].[test that the error is returned]
AS
BEGIN
DECLARE #ErrorMsg VARCHAR(50)
EXEC tsqlt.FakeTable #TableName = 'MyTable'
EXEC tsqlt.ApplyTrigger 'MyTable', 'MyTable_IUDTR'
BEGIN TRY
INSERT INTO dbo.MyTable
( FirstName, LastName )
VALUES ( N'John',N'Smith')
END TRY
BEGIN CATCH
SET #ErrorMsg = ERROR_MESSAGE()
END CATCH
EXEC tSQLt.AssertEqualsString #Expected = 'MyError', #Actual = #ErrorMsg
END
GO

You may be using SET XACT_ABORT ON in your tsqlt test
CREATE PROC [testClass].[test foo]
AS
BEGIN
SET XACT_ABORT ON
EXEC tsqlt.AssertEquals #Expected = 1, #Actual = 0
END
Don't do this.
You'll get a message like this
[testClass].[test foo] failed: (Error) Expected: <1> but was: <0> (There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.{Private_RunTest,160})

Related

"The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION" I keep getting this error when I try to execute my stored procedure,Help me

ALTER PROCEDURE Add_Edit_Courses_new
#CourseCode VARCHAR,
... other params ...
AS
BEGIN TRY
DECLARE #ErrorCode INT =0, #ErrorMessage VARCHAR(25) = 'Action failed'
IF #TaskType > 2
BEGIN
RAISERROR('Wrong action key',16,1)
END
ELSE
BEGIN TRANSACTION
BEGIN
DECLARE #message VARCHAR(MAX)
IF #TaskType = 1
BEGIN
INSERT INTO Courses(...) VALUES(#CourseCode,...)
SET #message = 'Added Successfully'
END
ELSE IF #TaskType = 2
BEGIN
UPDATE Courses SET CourseCode=#CourseCode,...;
SET #message = 'Modified Successfully'
END
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
SELECT ERROR_NUMBER() AS ErrorNumber, ...
END CATCH
I wrote this stored procedure to insert and update and I used (1 & 2) to differentiate the task while using a try and catch, but every time I try to execute this stored procedure I keep getting that error, please can you help me with where I am wrong, I am just learning this principle for the first time.
Why is BEGIN TRANSACTION before BEGIN? I feel like BEGIN TRANSACTION/COMMIT TRANSACTION should be inside the ELSE conditional. With some noise removed:
IF #TaskType > 2
BEGIN
RAISERROR('Wrong action key',16,1);
END
ELSE
BEGIN -- moved this here
BEGIN TRANSACTION;
-- BEGIN -- removed this
DECLARE #message varchar(max);
IF #TaskType = 1
BEGIN
INSERT INTO Courses(...
SET #message = 'Added Successfully';
END
IF #TaskType = 2 -- don't really need ELSE there
BEGIN
UPDATE Courses SET ...
SET #message = 'Modified Successfully';
END
-- END -- removed this
COMMIT TRANSACTION;
SELECT #message;
END -- moved this here
In your catch you just blindly say:
ROLLBACK TRANSACTION;
This should be updated to:
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION;
END
Note that if you have a conditional where multiple statements are not wrapped correctly in BEGIN / END, they won't execute like you think. Consider:
IF 1 = 0
PRINT 'foo';
PRINT 'bar';
You get bar output every time, regardless of the result of the conditional.
You had something similar:
IF 1 = 1
-- do stuff
ELSE
BEGIN TRANSACTION;
BEGIN
-- do stuff
END
COMMIT TRANSACTION;
In that case, -- do stuff and the commit happened every time, even if the begin transaction did not, because that BEGIN/END wrapper (and anything that followed it) was not associated with the ELSE.
Perhaps a little dry and wordy if you're new to the topic, but Erland Sommarskog has a very comprehensive series on error handling here, might be worth a bookmark:
https://www.sommarskog.se/error_handling/Part1.html
https://www.sommarskog.se/error_handling/Part2.html
https://www.sommarskog.se/error_handling/Part3.html
Imo there are some major issues with this code. First, if the intention is for the transaction to be atomic then SET XACT_ABORT ON should be specified. Per the Docs RAISERROR does not honor SET XACT_ABORT ON so it could be converted to use THROW. Second, in cases where the ELSE block in the code is hit then COMMIT will always be hit (regardless of what happens to the commitable state of the transaction).
Also, the code performs the ROLLBACK before the code:
SELECT ERROR_NUMBER() AS ErrorNumber, ...
It's the ROLLBACK which clears the error messages and returns the state to normal. To catch the error metadata SELECT it before the ROLLBACK happens.

SQL Server - error after executing stored procedure

When executing my stored procedure, why do I get the following error message?
Msg 266, Level 16, State 2, Procedure spAddCustomer, Line 0 [Batch Start Line 21]
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 2.
Anything help, thanks.
Stored procedure code:
CREATE PROC spAddCustomer
#FirstName VARCHAR = INPUT,
#LastName VARCHAR = INPUT,
#EmailAddress VARCHAR = INPUT,
#PhoneNumber VARCHAR = INPUT
AS
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO sales.CustomerPII (FirstName, LastName, EmailAddress, PhoneNumber)
VALUES (#FirstName, #LastName, #EmailAddress, #PhoneNumber);
COMMIT TRANSACTION
END TRY
BEGIN CATCH
--Rows inserted still exist
--SELECT ERROR_NUMBER()
--ROLLBACK TRANSACTION --Any transaction work will be undone
END CATCH;
Executed
EXEC spAddCustomer 'FirstTest', 'LastTest', 'EmailTest', 'AddressTest';
Un-comment this line:
ROLLBACK TRANSACTION --Any transaction work will be undone
Try setting XACT_ABORT ON in the stored procedure. When SET XACT_ABORT is ON and T-SQL statement raises a run-time error, SQL Server automatically rolls back the current transaction. Try it as follows:
USE AdventureWorks2016CTP3
GO
CREATE PROC spAddCustomer
#FirstName varchar = INPUT,
#LastName varchar = INPUT,
#EmailAddress varchar = INPUT,
#PhoneNumber varchar = INPUT
AS
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO sales.CustomerPII (FirstName,LastName,EmailAddress,PhoneNumber)
VALUES(#FirstName, #LastName, #EmailAddress, #PhoneNumber);
COMMIT TRANSACTION
END TRY
BEGIN CATCH
END CATCH;

Return error message from stored procedure

The question should be quite simple, but I can't figure out the answer nor why my stored procedure is not working.
CREATE PROCEDURE spTest_Delete
#ID int
AS
begin tran
declare #err int
declare #errMesage nvarchar(max)
set #errMesage = ''
set #err = 0
delete from Test
where ID = #ID
set #err = ##ERROR
set #errMesage = ERROR_MESSAGE()
if #err = 0
commit tran
else
begin
RAISERROR(N'Could not delete !Error nr: %d. Message: %s', 1, 16, #err, #errMesage)
rollback tran
end
This procedure runs ok, but in case of FK constraint on the delete statement it runs into an error (which is good) and I would like to catch the error.
Msg 547, Level 16, State 0, Procedure spTest_Delete, Line 12
The DELETE statement conflicted with the REFERENCE constraint
"FK_TEstFK_Test". The conflict occurred in database "Test", table
"dbo.Test", column 'ID'. The statement has been terminated.
Could not delete!
Error nr: 547. Message: (null) Msg 50000, Level 1, State 16
I always get null for my message variable, even though the delete statement throws an error.
You might want to start using TRY..CATCH block in your procedures
Implements error handling for Transact-SQL that is similar to the
exception handling in the Microsoft Visual C# and Microsoft Visual C++
languages. A group of Transact-SQL statements can be enclosed in a TRY
block. If an error occurs in the TRY block, control is passed to
another group of statements that is enclosed in a CATCH block.
So your procedure could be rewritten as:
CREATE PROCEDURE spTest_Delete #ID INT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION
DELETE
FROM Test
WHERE ID = #ID;
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
SELECT ERROR_NUMBER(), ERROR_MESSAGE();
END CATCH
END
Also, please note that you're running as single delete statement. It means that it doesn't need to be wrapped up in a transaction. This question explains why.
Your code becomes this:
CREATE PROCEDURE spTest_Delete #ID INT
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
DELETE
FROM Test
WHERE ID = #ID;
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER(), ERROR_MESSAGE();
END CATCH
END
Now why your #errMessage is always NULL? Because ERROR_MESSAGE() is valid ONLY IN CATCH BLOCK. That's written in documentation:
Returns the message text of the error that caused the CATCH block of a
TRY…CATCH construct to be run.
Using TRY..CATCH in Transact-SQL tells this:
Error information is retrieved by using these functions from anywhere
in the scope of the CATCH block of a TRY…CATCH construct. The error
functions will return NULL if called outside the scope of a CATCH
block.
Try to use TRY CATCH and catch your error like this:
BEGIN TRY
delete from Test
where ID = #ID
END TRY
BEGIN CATCH
SET #ErrorMessage = ERROR_MESSAGE()
SET #ErrorSeverity = ERROR_SEVERITY()
SET #ErrorState = ERROR_STATE()
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState)
BREAK
END CATCH

How to get error description once error occured during sql statement execution

I am using SQL Server 2008 and writing a stored procedure which inserts and updates records in a table. This procedure will be scheduled for some time. Whenever there is a error I am logging that error in another table. But I need actual error message but unable to get the same.
I have used this:
SET #ErrNum = ##ERROR
SET #ErrMsg = ERROR_MESSAGE()
But my problem is I'm not getting the actual error message. I am able to get error number.
Please suggest what needs to be done for actual message to get from SQL Server prompt after execution of SQL statement.
ERROR_MESSAGE() should do the trick.
BEGIN TRY
SELECT 1/0
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
END CATCH;
This is valid what you're doing. In all scripts you can see something like that
DECLARE #ErrorMessage nvarchar(max)
DECLARE #ErrorMessageOriginal nvarchar(max)
DECLARE #Error int
DECLARE #ReturnCode int
SET #Error = 0
SET #ReturnCode = 0
SET #Error = ERROR_NUMBER()
SET #ReturnCode = #Error
SET #ErrorMessageOriginal = ERROR_MESSAGE()
SET #ErrorMessage = 'Msg ' + CAST(#Error AS nvarchar) + ', ' + ISNULL(#ErrorMessageOriginal,'')
RAISERROR(#ErrorMessage,16,1) WITH NOWAIT
But there is error that you want to handle by yourself like that from MSDN source:
Using ##ERROR to detect a specific error
USE AdventureWorks2012;
GO
UPDATE HumanResources.EmployeePayHistory
SET PayFrequency = 4
WHERE BusinessEntityID = 1;
IF ##ERROR = 547
PRINT N'A check constraint violation occurred.';
GO

SSIS Package and Raising Error

I apologize is this is a repeat. I couldn't find a question that was answered that fit what I was asking. (this is similar How can i create out parameters with RAISERROR in stored procedure?)
I have a very simple job in SQL Server Agent with one step. The package only executes a stored procedure. The stored procedure has a try / catch block and when I try to cause an exception it is caught but doesn't propagate up and cause the job to report an error. The job succeeds. I log the error into a local table and commit it so I know I am indeed entering the catch block. I have tried raising error with severity levels 16 and 20 and some other things that were probably not worth mentioning.
Is this a package setting that would cause it to not propagate or is a problem with my stored procedure? Thanks.
-- dumbed down structure
BEGIN
SET NOCOUNT ON;
-- buncha vars
BEGIN TRY
BEGIN TRANSACTION
set #StepNumber = 10;
select 1/0;
COMMIT TRANSACTION;
RETURN 0;
END TRY
BEGIN CATCH
-- get the error
SELECT #ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
-- rollback the transaction
ROLLBACK TRANSACTION;
-- log the caught exception into the local log
set #LogTimestamp = GETUTCDATE();
set #LogMessage = #SessionGUID + ' - ' + CAST(#StepNumber AS NVARCHAR) + ' - ' + ' ERROR - ENTERED CATCH BLOCK - ERROR_MESSAGE()=' + #ErrorMessage + ' ERROR_SEVERITY()=' + cast(#ErrorSeverity as nvarchar) + ' ERROR_STATE()=' + CAST(#ErrorState as nvarchar) ;
insert into #LocalLog(TimestampDT, SourceVch, MessageVch) values (#LogTimestamp, #LogSource, #LogMessage);
-- transfer local log to master log table
BEGIN TRANSACTION;
--insert into RealTable selecting from #LocalLog;
COMMIT TRANSACTION;
-- slightly modify the error message before raising it to parent process
SET #ErrorMessage = #LogSource + ' - error with #StepNumber=[' +CAST(#StepNumber as nvarchar)+ '] ERROR_MESSAGE()=' + #ErrorMessage;
-- rethrow the error to the calling process
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState);
-- do we need this return?
RETURN #StepNumber;
END CATCH
END
The log table I get tells me I caught the error at least (From my log table).
"6a3e80fd-f480-459c-a42f-25fd3d5a42a8 - 125 - ERROR - ENTERED CATCH BLOCK - ERROR_MESSAGE()=Divide by zero error encountered. ERROR_SEVERITY()=16 ERROR_STATE()=1"

Resources