I have following definition of stored procedure:
CREATE procedure dbo.ImportData
(
#SessionId VARCHAR(20)
,#ImportId int
)
as
begin
PRINT 'TRANCOUNT value = ' + CAST(##TRANCOUNT AS VARCHAR)
begin try
--business log
begin try
BEGIN CATCH
--business log
END CATCH
END
Whenever i am running my SP, i am getting ##Trancount value as 1 however i have not begin any transaction using BEGIN TRAN statement.
Please suggest this behavior of sql server of creating a transition after BEGIN statement of the SP definition.
Also, when i tried to replicate the same behavior by creating another SP without parameter and only one SELECT statement but i am getting Transition value as 0.
Please suggest this concept.
You have already opened transaction (maybe accidentally).
Try to disconnect from sql-server or commit (rollbcack) existing transactions.
Related
I am new to using Transact-SQL, and I have a question on how transactions within nested stored procedures would be handled.
Consider the following example, where we create an example table as follows:
CREATE TABLE EXAMPLE_TABLE
(
ID INT,
NAME VARCHAR(255)
);
Then, we create a stored procedure with no parameters. This stored procedure involves inserting values into the table from above.
CREATE PROCEDURE SP1
AS
BEGIN
BEGIN TRANSACTION
INSERT INTO EXAMPLE_TABLE (ID, NAME)
VALUES (1, 'BOB')
COMMIT TRANSACTION;
END;
And then we create a second stored procedure with one parameter that calls our first stored procedure.
CREATE PROCEDURE sp2
#EXAMPLE INT
AS
BEGIN
BEGIN TRANSACTION
EXEC SP1
IF (#EXAMPLE < 10)
ROLLBACK TRANSACTION;
ELSE
COMMIT TRANSACTION;
END;
And then we call our second stored procedure as follows:
EXEC sp2 #EXAMPLE = 5;
At the end of this execution, will the values have been added to the EXAMPLE_TABLE? Or does the rollback in the outer stored procedure mean that everything has been rolled back, and nothing committed?
Transactions are scoped, so anything within a transaction is committed/rolled back together. So a value of 5 on your #example variable would prevent records from being added to the EXAMPLE_TABLE. You can check this fiddle for a demo.
I will add that if this example is in anyway similar to actual code you'll be writing, I would suggest to just check the variable value and make a decision on whether or not to run the insert stored procedure in the first place.
The conclusion of Aaron's answer is correct, but the reasoning is a little misleading.
Transactions aren't really "scoped" in the usual way you would think of scoping. The outermost begin tran does of course begin a transaction. But any nested begin tran doesn't really do anything other than increment the ##trancount. Then, when you commit, this doesn't really commit anything unless ##trancount is 1. Only the outermost commit is a "real" commit. Finally, a rollback will rollback everything, not just the current, "most nested" transaction, returning ##trancount to 0. At that point, if you try to commit or rollback you will get an error:
begin tran
print ##trancount
begin tran
print ##trancount
rollback
print ##trancount
commit
1
2
0
Msg 3902, Level 16, State 1, Line 61
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
For this reason, as a stylistic guide when actually coding transactions, I strongly suggest not treating a begin tran as the start of a block which needs to be indented. Treat begin tran, commit and rollback as regular statements, not the start and end of blocks.
The only exception to this behaviour is when you begin a named transaction, in which case you can rollback to the start of that named transaction.
I am trying to insert almost 1,75,00,000 in 8 tables.
I have stored procedure for that. At the start and end of that procedure, I have written Transaction.
Error: The transaction log for database 'Database' is full due to 'ACTIVE_TRANSACTION'.
Note: I want to keep everything in the transaction, its automated process. This process will run on Database every month
CREATE OR ALTER PROCEDURE [dbo].[InsertInMainTbls]
AS
BEGIN
PRINT('STARTED [InsertInMainTbls]')
DECLARE #NoRows INT
DECLARE #maxLoop INT
DECLARE #isSuccess BIT=1
BEGIN TRY
BEGIN TRAN
--1st table
SET #NoRows = 1
SELECT #maxLoop=(MAX([col1])/1000)+1 FROM ProcessTbl
SELECT 'loop=='+ CAST(#maxLoop as Varchar)
WHILE (#NoRows <= #maxLoop)
BEGIN
INSERT INTO MainTbl WITH(TABLOCK)
( col1,col2,col3....col40)
SELECT
( val1,val2,val3....val40)FROM
ProcessTbl
WHERE [col1] BETWEEN (#NoRows*1000)-1000
AND (#NoRows*1000)-1
SET #NoRows = #NoRows+1;
END
--2nd table
.
.
.
--8th table
SET #isSuccess=1;
COMMIT TRAN
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
SELECT ERROR_MESSAGE() 'ErrorMsg' ;
SET #isSuccess=0;
ROLLBACK TRAN
END CATCH
Despite the fact that is a nonsense to have such a huge transaction, while you can do a manual rollback by tagging the rows with something like a timesptamp or a GUID, to do so, you need to have the necessary space in the transaction log file to store all the rows of all your inserts from the first one til the last one, plus all the transaction that other user swill do at the same time. Many solutions to solve your problem :
1) enlarge your transaction log file
2) add some complementary log files
3) remove or decrease the transaction scope
I have a larger stored procedure which utilizes several TRY/CATCH blocks in order to catch and log individual errors. I have also wrapped a transaction around the entire contents of the procedure, so as to be able to roll back the entire thing in the event of an error raised somewhere along the way (in order to prevent a lot of messy cleanup); XACT_ABORT has been enabled since it would otherwise not roll back the entire transaction.
Key component:
There is a table in my database which gets a record inserted each time this procedure is run with the results of operations and details on what went wrong.
Funny thing is happening - actually, when I finally figured out what was wrong, it was pretty obvious... the the insert statement into my log table is getting rolled back as well, hence, if I am not running this out of SSMS, I will not be able to see that this was even run, as the rollback removes all trances of activity.
Question:
Would it be possible to have the entire transaction roll back with the exception of this single insert statement? I would still want to preserve the error message which I compile during the running of the stored procedure.
Thanks so much!
~Eli
Update 6/28
Here's a code sample of what I'm looking at. Key difference between this and the samples posed by #Alex and #gameiswar is that in my case, the try/catch blocks are all nested inside the single transaction. The purpose of this is to have multiple catches (for the multiple tables), though we would the entire mess to be rolled back even if the last update failed.
SET XACT_ABORT ON;
BEGIN TRANSACTION
DECLARE #message AS VARCHAR(MAX) = '';
-- TABLE 1
BEGIN TRY
UPDATE TABLE xx
SET yy = zz
END TRY
BEGIN CATCH
SET #message = 'TABLE 1 '+ ERROR_MESSAGE();
INSERT INTO LOGTABLE
SELECT
GETDATE(),
#message
RETURN;
END CATCH
-- TABLE 2
BEGIN TRY
UPDATE TABLE sss
SET tt = xyz
END TRY
BEGIN CATCH
SET #message = 'TABLE 2 '+ ERROR_MESSAGE();
INSERT INTO LOGTABLE
SELECT
GETDATE(),
#message
RETURN;
END CATCH
COMMIT TRANSACTION
You can try something like below ,which ensures you log the operation.This takes advantage of the fact that table variables dont get rollbacked..
Psuedo code only to give you idea:
create table test1
(
id int primary key
)
create table logg
(
errmsg varchar(max)
)
declare #errmsg varchar(max)
set xact_abort on
begin try
begin tran
insert into test1
select 1
insert into test1
select 1
commit
end try
begin catch
set #errmsg=ERROR_MESSAGE()
select #errmsg as "in block"
if ##trancount>0
rollback tran
end catch
set xact_abort off
select #errmsg as "after block";
insert into logg
select #errmsg
select * from logg
OK... I was able to solve this using a combination of the great suggestions put forth by Alex and GameisWar, with the addition of the T-SQL GOTO control flow statement.
The basic ideas was to store the error message in a variable, which survives a rollback, then have the Catch send you to a FAILURE label which will do the following:
Rollback the transaction
Insert a record into the log table, using the data from the aforementioned variable
Exit the stored procedure
I also use a second GOTO statement to make sure that a successful run will skip over the FAILURE section and commit the transaction.
Below is a code snippet of what the test SQL looked like. It worked like a charm, and I have already implemented this and tested it (successfully) in our production environment.
I really appreciate all the help and input!
SET XACT_ABORT ON
DECLARE #MESSAGE VARCHAR(MAX) = '';
BEGIN TRANSACTION
BEGIN TRY
INSERT INTO TEST_TABLE VALUES ('TEST'); -- WORKS FINE
END TRY
BEGIN CATCH
SET #MESSAGE = 'ERROR - SECTION 1: ' + ERROR_MESSAGE();
GOTO FAILURE;
END CATCH
BEGIN TRY
INSERT INTO TEST_TABLE VALUES ('TEST2'); --WORKS FINE
INSERT INTO TEST_TABLE VALUES ('ANOTHER TEST'); -- ERRORS OUT, DATA WOULD BE TRUNCATED
END TRY
BEGIN CATCH
SET #MESSAGE = 'ERROR - SECTION 2: ' + ERROR_MESSAGE();
GOTO FAILURE;
END CATCH
GOTO SUCCESS;
FAILURE:
ROLLBACK
INSERT INTO LOGG SELECT #MESSAGE
RETURN;
SUCCESS:
COMMIT TRANSACTION
I don't know details but IMHO general logic can be like this.
--set XACT_ABORT ON --not include it
declare #result varchar(max) --collect details in case you need it
begin transaction
begin try
--your logic here
--if something wrong RAISERROR(...#result)
--everything OK
commit
end try
begin catch
--collect error_message() and other into #result
rollback
end catch
insert log(result) values (#result)
I have written a procedure like below lines of code
ALTER PROCEDURE [dbo].[CountrySave]
(
#CountryId uniqueidentifier,
#CountryName nvarchar(max)
)
AS
begin tran
if exists (select * from Country where CountryID =#CountryId)
begin
update Country set
CountryID = #CountryId,
CountryName =#CountryName
where CountryID = #CountryId
end
else
begin
insert INTO Country(CountryID, CountryName) values
(NewID(),#CountryName)
end
It throws "Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
A transaction that was started in a MARS batch is still active at the end of the batch. The transaction is rolled back." error message when executed!!!
Please Help...
Add COMMIT TRAN
ALTER PROCEDURE [dbo].[CountrySave]
#CountryId uniqueidentifier,
#CountryName nvarchar(max)
AS
BEGIN
BEGIN TRY
BEGIN TRAN
if exists (select * from Country where CountryID =#CountryId)
begin
update Country
set CountryID = #CountryId,
CountryName =#CountryName
where CountryID = #CountryId;
end
else
begin
insert INTO Country(CountryID, CountryName)
values(NewID(),#CountryName)
end
COMMIT TRAN
END TRY
BEGIN CATCH
/* Error occured log it */
ROLLBACK
END CATCH
END
The error message is fairly clear. When you open (begin) a transaction, you will need to do something at the end of it as well.
So either you ROLLBACK the transaction (in case one of the statements within the transaction fails), or you COMMIT the transaction in order to actually implement all changes your statements made.
From MSDN:
BEGIN TRANSACTION represents a point at which the data referenced by a
connection is logically and physically consistent. If errors are
encountered, all data modifications made after the BEGIN TRANSACTION
can be rolled back to return the data to this known state of
consistency. Each transaction lasts until either it completes without
errors and COMMIT TRANSACTION is issued to make the modifications a
permanent part of the database, or errors are encountered and all
modifications are erased with a ROLLBACK TRANSACTION statement.
More information: https://msdn.microsoft.com/en-us/library/ms188929.aspx
Your Problem is that you begin a transaction but you never commit it / do a rollback.
Try this structure for your procedure, worked very well for me in the past:
CREATE PROCEDURE [dbo].SomeProc
(#Parameter INT)
AS
BEGIN
--if you want to be to only active transaction then uncomment this:
--IF ##TRANCOUNT > 0
--BEGIN
-- RAISERROR('Other Transactions are active at the moment - Please try again later',16,1)
--END
BEGIN TRANSACTION
BEGIN TRY
/*
DO SOMETHING
*/
COMMIT TRANSACTION
END TRY
BEGIN CATCH
--Custom Error could be raised here
--RAISERROR('Something bad happened when doing something',16,1)
ROLLBACK TRANSACTION
END CATCH
END
BEGIN TRAN
SET XACT_ABORT ON
GO
BEGIN TRY
IF OBJECT_ID('dbo.Offer_GetByStudyId', 'p') IS NULL
EXEC ('CREATE PROCEDURE Offer_GetByStudyId as select 1')
END TRY
BEGIN CATCH
THROW;
END CATCH
GO
IF ##error <> 0 and ##trancount > 0 ROLLBACK
IF ##trancount = 0 BEGIN SET NOCOUNT ON; SET NOEXEC ON; END
GO
BEGIN TRY
ALTER PROCEDURE dbo.Offer_GetByStudyId
#StudyId NVARCHAR(MAX) = NULL
AS
BEGIN
DECLARE #Conditions NVARCHAR(MAX) = '';
IF #StudyId IS NOT NULL
BEGIN
SET #Conditions = #Conditions + ' AND o.StudyId = ' + cast(#StudyId as varchar(10))
END
DECLARE #sql NVARCHAR(MAX) = 'SELECT
o.StudyId as StudyId,
o.SampleId as SampleId,
o.Status as Status,
o.Title as Title,
o.Topic as Topic,
o.Description as Description,
o.TestOffer as TestOffer,
T.CPI as CPI
FROM Offers o
LEFT JOIN [dbo].[Terms] T ON (o.[Id] = T.[OfferId]) AND T.Active = 1
WHERE 1 = 1' + #Conditions
EXEC(#sql)
END
END TRY
BEGIN CATCH
THROW;
END CATCH
This is my SQL script, I'm trying to have a multi batch script run as a single transaction, so if one statement fails, all of it will be rolled back. But I here keep getting this error:
Incorrect syntax near BEGIN. expecting EXTERNAL
The begin they are talking about is the one after:
#StudyId NVARCHAR(MAX) = NULL
You need to remove your "GO" statements. These are not part of the TSQL language, they are just statements to tell SSMS/SQLCMD that the batch above should be executed. I'm not clear on the behavior of a transaction split across several GO statements. I would start my removing the "GO"s.
https://msdn.microsoft.com/en-us/library/ms188037.aspx
If you take out the statement ALTER PROCEDURE dbo.Offer_GetByStudyId (Line 22 - 47) from TRY CATCH it will work. i.e delete TRY CATCH (Line 20 and Line 48 - 51)
I've realised I've buried the answer in the below, so I'll bring it up to the top to make it clearer: you cannot wrap any flow control statements around an attempt to create or alter a procedure
A simpler example demonstrates the problem:
create procedure ABC
as
go
select * from sys.objects
alter procedure ABC as
Which produces the message:
Msg 111, Level 15, State 1, Procedure ABC, Line 2
'CREATE/ALTER PROCEDURE' must be the first statement in a query batch.
This is explicitly documented for CREATE PROCEDURE but for some reason isn't for ALTER PROCEDURE.
The CREATE PROCEDURE statement cannot be combined with other Transact-SQL statements in a single batch.
So the upshot is, you cannot wrap any flow control statements around an attempt to create or alter a procedure.
The reason is actually pretty simple - BEGIN and END aren't required around the body of the stored procedure - and a stored procedure can actually contain multiple "top-level" BEGIN/END pairs:
create procedure ABC as
begin
select * from sys.objects
end
begin
select * from sys.columns
end
is fine - so the only way that SQL Server knows the extent of a stored procedure, when it's being defined, is "from the CREATE/ALTER PROCEDURE until the end of the batch."