0

I wrote this stored procedure in SQL Server 2012 Express.

ALTER PROCEDURE [Dictionaries].[InsertCountry] (@p_countryName nvarchar(128) , @ret_countryId int OUTPUT) AS BEGIN DECLARE @TransactionName VARCHAR(20) = 'INST_COUNTRY'; BEGIN TRANSACTION @TransactionName; IF len(@p_countryName) <= 3 BEGIN SET @ret_countryId = null ROLLBACK TRANSACTION @TransactionName; END INSERT INTO [Dictionaries].[CountryDetails] (name) VALUES (@p_countryName); SET @ret_countryId = @@IDENTITY INSERT INTO [Dictionaries].[Places] (name, Discriminator , CountryDetails_Id, RegionDetails_Id, CityDetails_Id) VALUES (@p_countryName, 'Country' , @ret_countryId, null, null); COMMIT TRANSACTION @TransactionName; END 

I use this method in this way, but I get message

The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.

Code:

declare @ret int = null EXEC [Dictionaries].[InsertCountry] 'us', @ret output 

What is wrong in this code?

2 Answers 2

5

First you rollback, then you commit. This is a bug. Probably, you meant to return after the rollback.

Sign up to request clarification or add additional context in comments.

2 Comments

After rollback, I want to escape from stored procedure. Just add RETURN keyword after rollback?
Yes, just use normal control flow. Make sure not to execute the commit. Do that any way you like.
0

You could use one of stored procedure templates / boilerplate provided by Microsoft: here(example B or C; for non-nested transactions) or here(for nested transactions). Also, you could use RAISERROR to generate an exception if some conditions aren't meet.

Example (based on the last template - [nested transactions]):

IF EXISTS (SELECT name FROM sys.objects WHERE name = N'dbo.Country_Insert') DROP PROCEDURE dbo.Country_Insert; GO CREATE PROCEDURE dbo.Country_Insert ( @p_countryName nvarchar(128) ,@ret_countryId int OUTPUT ) AS -- Detect whether the procedure was called -- from an active transaction and save -- that for later use. -- In the procedure, @TranCounter = 0 -- means there was no active transaction -- and the procedure started one. -- @TranCounter > 0 means an active -- transaction was started before the -- procedure was called. DECLARE @TranCounter INT; SET @TranCounter = @@TRANCOUNT; IF @TranCounter > 0 -- Procedure called when there is -- an active transaction. -- Create a savepoint to be able -- to roll back only the work done -- in the procedure if there is an -- error. SAVE TRANSACTION ProcedureSave; ELSE -- Procedure must start its own -- transaction. BEGIN TRANSACTION; -- Modify database. BEGIN TRY -- Custom source code IF len(@p_countryName) <= 3 BEGIN SET @ret_countryId = null RAISERROR('Wrong contry name', 16, 1); END INSERT INTO [Dictionaries].[CountryDetails] (name) VALUES (@p_countryName); SET @ret_countryId = SCOPE_IDENTITY() -- @@IDENTITY isn't safe INSERT INTO [Dictionaries].[Places] (name, Discriminator , CountryDetails_Id, RegionDetails_Id, CityDetails_Id) VALUES (@p_countryName, 'Country' , @ret_countryId, null, null); -- End of Custom source code -- Get here if no errors; must commit -- any transaction started in the -- procedure, but not commit a transaction -- started before the transaction was called. IF @TranCounter = 0 -- @TranCounter = 0 means no transaction was -- started before the procedure was called. -- The procedure must commit the transaction -- it started. COMMIT TRANSACTION; END TRY BEGIN CATCH -- An error occurred; must determine -- which type of rollback will roll -- back only the work done in the -- procedure. IF @TranCounter = 0 -- Transaction started in procedure. -- Roll back complete transaction. ROLLBACK TRANSACTION; ELSE -- Transaction started before procedure -- called, do not roll back modifications -- made before the procedure was called. IF XACT_STATE() <> -1 -- If the transaction is still valid, just -- roll back to the savepoint set at the -- start of the stored procedure. ROLLBACK TRANSACTION ProcedureSave; -- If the transaction is uncommitable, a -- rollback to the savepoint is not allowed -- because the savepoint rollback writes to -- the log. Just return to the caller, which -- should roll back the outer transaction. -- After the appropriate rollback, echo error -- information to the caller. DECLARE @ErrorMessage NVARCHAR(4000); DECLARE @ErrorSeverity INT; DECLARE @ErrorState INT; SELECT @ErrorMessage = ERROR_MESSAGE(); SELECT @ErrorSeverity = ERROR_SEVERITY(); SELECT @ErrorState = ERROR_STATE(); RAISERROR (@ErrorMessage, -- Message text. @ErrorSeverity, -- Severity. @ErrorState -- State. ); END CATCH GO 

Note: As you can see, I've used SCOPE_IDENTITY() instead of @@IDENTITY. The reason is explained here.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.