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;
Related
This is my stored procedure
ALTER Proc [dbo].[DeleteQualityAssemblyProduction]
#id int,
#Quantity int,
#idPartShip int,
#FK_idNextProcess int
AS
DELETE FROM [dbo].DailyQualityAssemblyProduction
WHERE id=#id
if #FK_idNextProcess=11
Begin
UPDATE [dbo].[ProjectShipping]
SET
QualityAssemblyQty = QualityAssemblyQty- #Quantity
WHERE id=#idPartShip
End
I want when both DELETE and UPDATE run successfully COMMIT the changes otherwise ROLLBACK .
I was wondering if adding COMMIT in the end of stored procedure do the job or I need other method
Here is one way you could tackle this. This is adding a transaction which you will need to handle multiple DML statements in one autonomous block. Then added a try/catch so that if either statement fails the transaction will deal with both statements as one unit of work.
ALTER Proc [dbo].[DeleteQualityAssemblyProduction]
(
#id int,
#Quantity int,
#idPartShip int,
#FK_idNextProcess int
) AS
set nocount on;
begin transaction
begin try
DELETE FROM [dbo].DailyQualityAssemblyProduction
WHERE id = #id
if #FK_idNextProcess = 11
begin
UPDATE [dbo].[ProjectShipping]
SET QualityAssemblyQty = QualityAssemblyQty - #Quantity
WHERE id = #idPartShip
end
commit transaction
end try
begin catch
rollback transaction
declare #error int
, #message varchar(4000);
select #error = ERROR_NUMBER()
, #message = ERROR_MESSAGE()
raiserror ('DeleteQualityAssemblyProduction: %d: %s', 16, 1, #error, #message) ;
end catch
InsertCity stored procedure , to insert city name in database.
After successful insert, log who has done it (using insertlog stored procedure) .
These steps should be atomic.
This is my SQL Server Code:
ALTER PROCEDURE [paycom].[InsertCity]
(
#name nvarchar(50),
#employeeID int
)
AS
Begin Atomic
SET NOCOUNT OFF;
declare #cityID int
INSERT INTO City (CityName) VALUES (#name)
select #cityID = Scope_IDentity()
exec InsertLog #employeeID, #name, #cityID
return #cityID
End
and I get this errors:
Msg 156, Level 15, State 1, Procedure InsertCity, Line 15 Incorrect
syntax near the keyword 'SET'. Msg 102, Level 15, State 1, Procedure
InsertCity, Line 23 Incorrect syntax near 'End'.
Any idea about?
without begin atomic, it works properly!
What you really mean is to use a transaction and proper error handling.
And don't use RETURN to return values
SET NOCOUNT OFF;
Declare #cityID int;
Begin TRY
BEGIN TRANSACTION;
INSERT INTO City (CityName) VALUES (#name)
select #cityID = Scope_IDentity()
exec InsertLog #employeeID, #name, #cityID
COMMIT TRANSACTION;
SELECT #cityID;
End try
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK TRANSACTION;
THROW;
END CATCH
I doubt you really want to use in-memory OLTP etc
The following options are required with BEGIN ATOMIC: Required
Setting TRANSACTION ISOLATION LEVEL LANGUAGE
https://msdn.microsoft.com/eu-es/library/dn452281(v=sql.120).aspx
if a membered does not exist in Notes table, then the insert line before should be rolledback. I thought by putting both lines in the Begin transaction, it would rollback, but because no error is thrown, it executes the insert command. How do I throw an error if membered does not exist, so that the whole block of code is rolled back?
begin try
begin transaction
insert into notes (memberid, NoteEffDate, LoginName, NoteDesc) values (367737, GETDATE(), 'marc', 'blah blah')
IF EXISTS (SELECT memberid FROM notes WHERE memberid =4774769)
begin
update notes set notedesc = 'hello there' where memberid = 4774769
end
commit transaction
end try
begin catch
rollback transaction
select ERROR_MESSAGE()
end catch
go
It looks like you're trying to use some version of Sam Saffron's upsert method.
A rough version of what a procedure would look like for that:
create procedure dbo.notes_upsert (
#memberid int
, #notesdesc varchar(256)
, #loginname varchar(256)
) as
begin
set nocount on;
set xact_abort on;
begin tran
update notes (with serializable)
set notedesc = #notedesc
where memberid = #memberid;
if ##rowcount = 0
begin;
insert into notes (memberid, NoteEffDate, LoginName, NoteDesc)
values (#memberid, getdate(), #loginname, #notesdesc);
end;
commit tran
end;
You could RaiseError in your Try Block.
begin try
begin transaction
insert into notes (memberid, NoteEffDate, LoginName, NoteDesc) values (367737, GETDATE(), 'marc', 'blah blah')
IF EXISTS (SELECT memberid FROM notes WHERE memberid =4774769)
begin
update notes set notedesc = 'hello there' where memberid = 4774769
end
ELSE
-- RAISERROR with severity 11-19 will cause execution to
-- jump to the CATCH block.
RAISERROR ('Error raised in TRY block.', -- Message text.
16, -- Severity.
1 -- State.
);
commit transaction
end try
begin catch
rollback transaction
select ERROR_MESSAGE()
end catch
go
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})
This is the first time that I use transactions and I just wonder am I make this right. Should I change something?
I insert post(wisp). When insert post I need to generate ID in commentableEntity table and insert that ID in wisp table.
ALTER PROCEDURE [dbo].[sp_CreateWisp]
#m_UserId uniqueidentifier,
#m_WispTypeId int,
#m_CreatedOnDate datetime,
#m_PrivacyTypeId int,
#m_WispText nvarchar(200)
AS
BEGIN TRANSACTION
DECLARE #wispId int
INSERT INTO dbo.tbl_Wisps
(UserId,WispTypeId,CreatedOnDate,PrivacyTypeId,WispText)
VALUES
(#m_UserId,#m_WispTypeId,#m_CreatedOnDate,#m_PrivacyTypeId,#m_WispText)
if ##ERROR <> 0
BEGIN
ROLLBACK
RAISERROR ('Error in adding new wisp.', 16, 1)
RETURN
END
SELECT #wispId = SCOPE_IDENTITY()
INSERT INTO dbo.tbl_CommentableEntity
(ItemId)
VALUES
(#wispId)
if ##ERROR <> 0
BEGIN
ROLLBACK
RAISERROR ('Error in adding commentable entity.', 16, 1)
RETURN
END
DECLARE #ceid int
select #ceid = SCOPE_IDENTITY()
UPDATE dbo.tbl_Wisps SET CommentableEntityId = #ceid WHERE WispId = #wispId
if ##ERROR <> 0
BEGIN
ROLLBACK
RAISERROR ('Error in adding wisp commentable entity id.', 16, 1)
RETURN
END
COMMIT
Using try/catch based on #gbn answer:
ALTER PROCEDURE [dbo].[sp_CreateWisp]
#m_UserId uniqueidentifier,
#m_WispTypeId int,
#m_CreatedOnDate datetime,
#m_PrivacyTypeId int,
#m_WispText nvarchar(200)
AS
SET XACT_ABORT, NOCOUNT ON
DECLARE #starttrancount int
BEGIN TRY
SELECT #starttrancount = ##TRANCOUNT
IF #starttrancount = 0
BEGIN TRANSACTION
DECLARE #wispId int
INSERT INTO dbo.tbl_Wisps
(UserId,WispTypeId,CreatedOnDate,PrivacyTypeId,WispText)
VALUES
(#m_UserId,#m_WispTypeId,#m_CreatedOnDate,#m_PrivacyTypeId,#m_WispText)
SELECT #wispId = SCOPE_IDENTITY()
INSERT INTO dbo.tbl_CommentableEntity
(ItemId)
VALUES
(#wispId)
DECLARE #ceid int
select #ceid = SCOPE_IDENTITY()
UPDATE dbo.tbl_Wisps SET CommentableEntityId = #ceid WHERE WispId = #wispId
IF #starttrancount = 0
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 AND #starttrancount = 0
ROLLBACK TRANSACTION
RAISERROR ('Error in adding new wisp', 16, 1)
END CATCH
GO
You'd use TRY/CATCH since SQL Server 2005+
Your rollback goes into the CATCH block but your code looks good otherwise (using SCOPE_IDENTITY() etc). I'd also use SET XACT_ABORT, NOCOUNT ON
This is my template: Nested stored procedures containing TRY CATCH ROLLBACK pattern?
Edit:
This allows for nested transactions as per DeveloperX's answer
This template also allows for higher level transactions as per Randy's comment
i think its not good all the time ,but if you want to use more than one stored procedure same time its not good be cause each stored procedure handles the transaction independently
but in this case,you should use try catch block , for exception handling , and preventing keeping transaction open on when an exception raising
I've never considered it a good idea to put transactions in a stored procedure. I think it's much better to start a transaction at a higher level so that can better coordinate multiple database (e.g. stored procedure) calls and treat them all as a single transaction.