How to use BEGIN TRANSACTION with while loop in SQL Server? - sql-server

How to use BEGIN TRANSACTION with while loop in SQL Server?
This query never finishes perhaps because it stops and look for COMMIT TRANSACTION after inserting one row (when #cnt = 1) but I don't want to COMMIT TRANSACTION because I want to see results before committing.
BEGIN TRANSACTION
DECLARE #cnt INT = 0;
WHILE #cnt <= 100
BEGIN
DECLARE #offset INT = 1
INSERT INTO totalSales (col1, col2)
SELECT
'Col1', ROW_NUMBER() OVER (ORDER BY col2) + #offset
FROM
sales
SET #cnt = #cnt + 1;
END;
So how I can check result before commit in while loop?

You should create a BEGIN TRAN outer (general), and inside loop while create a BEGIN TRAN inner (with a trans name).
Inside loop, if are conditions to rollbacks only for this iteration i use SAVE TRAN savepoint for not lose previous trans.
I 've created an example tests in loop while with conditional inserts and rollback savepoint:
declare #num int
set #num = 0
--drop table #test
create table #test (
valor Varchar(100)
)
begin tran
while #num <= 5
begin
begin transaction tran_inner
insert into #test (valor) values ('INSERT 1 INNER -> ' + convert(varchar(10),#num))
save transaction sv_inner
insert into #test (valor) values ('INSERT 2 EVEN - SAVEPOINT -> ' + convert(varchar(10),#num))
if #num % 2 = 0 begin
commit transaction sv_inner
end else begin
rollback transaction sv_inner
end
insert into #test (valor) values ('INSERT 3 INNER -> ' + convert(varchar(10),#num))
set #num = #num + 1
if ##trancount > 0 begin
commit transaction tran_inner
end
end
select valor from #test;
if ##trancount > 0 begin
commit tran
end
Return rows: 1, 2 if iteration even, and 3.

In the same batch (within the same transaction) you can simply issue a SELECT command to see the updated content of the table. Changes will be persisted when the COMMIT TRANSACTION statement is executed or reverted on ROLLBACK.
CREATE TABLE test (id INT IDENTITY(1,1), x VARCHAR(32));
GO
BEGIN TRANSACTION;
INSERT INTO test (x) VALUES ('a');
INSERT INTO test (x) VALUES ('b');
SELECT * FROM test;
ROLLBACK TRANSACTION;
Example: http://sqlfiddle.com/#!6/e4910/2
Alternatively you can use the INSERT INTO .. OUTPUT construct to output the result of the INSERT statement.
Docs: https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql
Outside the batch (using a second connection), you can use READ UNCOMMITTED isolation level to be able to read records not committed yet.
Docs: https://technet.microsoft.com/en-us/library/ms189122(v=sql.105).aspx

If you are saying it never finishes it sounds to me like you are getting some blocking going on because that loop runs just fine.
https://www.mssqltips.com/sqlservertip/2429/how-to-identify-blocking-in-sql-server/
I HIGHLY recommend using Adam Machanic's sp_WhoIsActive for this as well: http://whoisactive.com

Related

SQL Server SET XACT_ABORT ON vs TRY...CATCH Block inside the stored procedure

I am working on a game database. I have a stored procedure that is executed by the game client in case of teleportation, login, logout, death, etc processes. The game client is hardcoded and is not editable by me.
I am doing stuff in my procedure such as if the character logs in to the game, then add item to the character's inventory.
For each different types of process, I have IF blocks and also I have TRY...CATCH blocks in the each "IF" blocks to be able to handle with any errors in my procedure.
So, my question is that it does make any sense using TRY...CATCH blocks in this way? Or should I use SET XACT_ABORT ON statement instead of TRY...CATCH? Which one is better? By the way, the situation of occurrence of any error in IF block, the block have to be completely ROLLBACK.
Also, my procedure is highly executed by game client. There was almost 800 online character always moving in game and executed my procedure. It should be executed as possible as fast.
ALTER PROCEDURE [dbo].[_AddLogChar]
#CharID INT,
#EventID TINYINT,
#Data1 INT,
#Data2 INT,
#strPos VARCHAR(64),
#Desc VARCHAR(128)
AS
---- !!! KILL PROCEDURE !!! ----
IF (#EventID NOT IN (4,6,20))
BEGIN
RETURN 0;
END
---- BATTLE ARENA | ACADEMY ----
IF (#EventID = 20)
BEGIN
BEGIN TRY
BEGIN TRANSACTION TRAN_Battle_Arena
-- Declaration of variables for battle area conditions
DECLARE #CharInBattle VARCHAR(64) = SUBSTRING(#strPos, 15, 6)
IF (#CharInBattle IN ('0x7edc','0x7edb','0x7ed7','0x7ed3','0x7dd3','0x7ada','0x7ad8','0x7ad7','0x7ad5','0x7ad4','0x79db','0x79da','0x79d8','0x79d7','0x79d5','0x79d4','0x74d6','0x73d7','0x73d6','0x73d5','0x73d4','0x72d7','0x72d6','0x72d5','0x72d4'))
BEGIN
DECLARE #KillingCharname VARCHAR(50) = (SELECT SUBSTRING(#Desc,(PATINDEX('%(%', #Desc)) + 1, ((PATINDEX('%)%', #Desc)) - (PATINDEX('%(%', #Desc))) - 1))
DECLARE #KilledCharname VARCHAR(64) = (SELECT CharName16 FROM SRO_VT_SHARD.._Char WITH (NOLOCK) WHERE CharID = #CharID)
IF((#KillingCharname IS NOT NULL) AND (#KilledCharname IS NOT NULL))
BEGIN
INSERT INTO LOG_BattleHonorRank (KillingCharname, KilledCharname, BattleRegion)
VALUES (#KillingCharname, #KilledCharname, #CharInBattle)
UPDATE SRO_VT_SHARD.._TrainingCamp
SET GraduateCount = (GraduateCount + 1),
EvaluationPoint = EvaluationPoint + 5
WHERE ID = (SELECT CampID FROM SRO_VT_SHARD.._TrainingCampMember WITH (NOLOCK) WHERE CharName = #KillingCharname)
UPDATE SRO_VT_SHARD.._TrainingCamp
SET EvaluationPoint = EvaluationPoint - 6
WHERE ID = (SELECT CampID FROM SRO_VT_SHARD.._TrainingCampMember WITH (NOLOCK) WHERE CharName = #KilledCharname)
END
END
COMMIT TRANSACTION TRAN_Battle_Arena
END TRY
BEGIN CATCH
SELECT
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage;
ROLLBACK TRANSACTION TRAN_Battle_Arena
END CATCH
RETURN 1;
END
---- JOB SYSTEM ----
IF(#EventID=6 AND (SELECT [Level] FROM SRO_VT_SHARD.dbo._CharTrijob WHERE CharID=#CharID)=7)
BEGIN
BEGIN TRY
BEGIN TRANSACTION TRAN_Job_System
---------------------------------------------------------------------------------------------------
-- Declaration of variables
---------------------------------------------------------------------------------------------------
DECLARE #Charname16 VARCHAR(64)=(SELECT Charname16 FROM SRO_VT_SHARD.dbo._Char WITH (NOLOCK) WHERE CharID=#CharID)
DECLARE #traderJID INT=(SELECT UserJID FROM SRO_VT_SHARD.dbo._User WITH (NOLOCK) WHERE CharID=#CharID)
DECLARE #SkillID INT
DECLARE #JobBuffLevel INT
---------------------------------------------------------------------------------------------------
-- Check users have any information in SK_Silk or not, if not then begin to addition
---------------------------------------------------------------------------------------------------
IF NOT EXISTS(SELECT JID FROM [SRO_VT_ACCOUNT].[dbo].[SK_Silk] WHERE JID=#traderJID)
BEGIN
INSERT INTO [SRO_VT_ACCOUNT].[dbo].[SK_Silk] (JID, silk_own, silk_gift, silk_point) VALUES(#traderJID, 0, 0, 0);
END
---------------------------------------------------------------------------------------------------
-- Check users have any information in LOG_CharJobStatus or not, if not then begin to addition
---------------------------------------------------------------------------------------------------
IF NOT EXISTS(SELECT CharID FROM SRO_VT_LOG..LOG_CharJobStatus WHERE CharID=#CharID)
BEGIN
INSERT INTO SRO_VT_LOG..LOG_CharJobStatus (CharID, Charname, RestartCount, ObtainedSilk) VALUES(#CharID, #Charname16, 0, 0)
END
---------------------------------------------------------------------------------------------------
-- Begin to add reward silk, restart count, obtained silk & job coins information
---------------------------------------------------------------------------------------------------
UPDATE [SRO_VT_ACCOUNT].[dbo].[SK_Silk] SET silk_own=(silk_own+10) WHERE JID=#traderJID;
UPDATE SRO_VT_LOG..LOG_CharJobStatus SET RestartCount=(RestartCount+1), ObtainedSilk=(ObtainedSilk+10), [Date]=GETDATE() WHERE CharID=#CharID
EXEC SRO_VT_SHARD.dbo._ADD_ITEM_EXTERN #Charname16,'ITEM_ETC_SD_TOKEN_02',4,0
---------------------------------------------------------------------------------------------------
-- Check users restart count modulus, if modulus 10 equals to 0, then begin to add advanced elixir scroll
---------------------------------------------------------------------------------------------------
IF((SELECT (RestartCount % 10) FROM SRO_VT_LOG..LOG_CharJobStatus WHERE CharID=#CharID)=0)
BEGIN
UPDATE SRO_VT_LOG..LOG_CharJobStatus SET Obtained_Advanced_Elixir=(Obtained_Advanced_Elixir+1), [Date]=GETDATE() WHERE CharID=#CharID
EXEC SRO_VT_SHARD.dbo._ADD_ITEM_EXTERN #Charname16,'ITEM_ETC_VENUS_ADVANCED_ELIXIR_SCROLL',1,0
END
---------------------------------------------------------------------------------------------------
-- Check users restart count modulus, if modulus 5 equals to 0, then begin to add job buff
---------------------------------------------------------------------------------------------------
IF((SELECT (RestartCount % 5) FROM SRO_VT_LOG..LOG_CharJobStatus WHERE CharID=#CharID)=0)
BEGIN
IF EXISTS (SELECT JobID FROM SRO_VT_SHARD.._TimedJob WITH (NOLOCK) WHERE CharID=#CharID AND JobID IN (33791,33792,33793,33794,33795,33796,33797,33798,33799,33800))
BEGIN
DELETE FROM SRO_VT_SHARD.._TimedJob WHERE CharID=#CharID AND JobID IN (33791,33792,33793,33794,33795,33796,33797,33798,33799,33800)
END
IF((SELECT BuffLevel FROM SRO_VT_LOG..LOG_CharJobStatus WHERE CharID=#CharID)<=10)
BEGIN
UPDATE SRO_VT_LOG..LOG_CharJobStatus SET BuffLevel=(BuffLevel+1), [Date]=GETDATE() WHERE CharID=#CharID
END
SET #JobBuffLevel=(SELECT BuffLevel FROM SRO_VT_LOG..LOG_CharJobStatus WHERE CharID=#CharID)
SELECT #SkillID=
(CASE
WHEN #JobBuffLevel=1 THEN 33791
WHEN #JobBuffLevel=2 THEN 33792
WHEN #JobBuffLevel=3 THEN 33793
WHEN #JobBuffLevel=4 THEN 33794
WHEN #JobBuffLevel=5 THEN 33795
WHEN #JobBuffLevel=6 THEN 33796
WHEN #JobBuffLevel=7 THEN 33797
WHEN #JobBuffLevel=8 THEN 33798
WHEN #JobBuffLevel=9 THEN 33799
WHEN #JobBuffLevel>=10 THEN 33800
ELSE 0
END)
IF (NOT EXISTS (SELECT JobID FROM SRO_VT_SHARD.._TimedJob WHERE JobID=#SkillID AND CharID=#CharID) AND (#SkillID>0))
BEGIN
INSERT INTO SRO_VT_SHARD.._TimedJob VALUES (#CharID,0,#SkillID,(SELECT DATEDIFF(SECOND,'19700101 00:00:00:000',(SELECT DATEADD(HOUR,72,GETUTCDATE())))),0,1,0,0,0,0,0,0,0,0)
END
END
---------------------------------------------------------------------------------------------------
-- Restart to users job level
---------------------------------------------------------------------------------------------------
UPDATE SRO_VT_SHARD.._CharTrijob SET [Level]=1, [Exp]=0, Contribution=0 WHERE CharID=#CharID
COMMIT TRANSACTION TRAN_Job_System
END TRY
BEGIN CATCH
SELECT
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage;
ROLLBACK TRANSACTION TRAN_Job_System
END CATCH
END
----==========================================================================================================----
-------------------------------------------- SILKPERPERIOD -----------------------------------------------
IF(#EventID=4 OR #EventID=6)
BEGIN
BEGIN TRY
BEGIN TRANSACTION TRAN_SilkPerPeriod
---------------------------------------------------------------------------------------------------
-- For login state
---------------------------------------------------------------------------------------------------
IF (#EventID=4)
BEGIN
IF NOT EXISTS(SELECT CharID FROM LOG_CharInOut WHERE CharID=#CharID)
BEGIN
INSERT INTO LOG_CharInOut (CharID,Char_Name,Is_Online,In_Date) VALUES(#CharID, (SELECT CharName16 FROM SRO_VT_SHARD.._Char WITH(NOLOCK) WHERE CharID=#CharID), 1, GETDATE());
END
IF EXISTS(SELECT CharID FROM LOG_CharInOut WHERE CharID=#CharID)
BEGIN
UPDATE LOG_CharInOut SET Is_Online=1, In_Date=GETDATE() WHERE CharID=#CharID
END
END
---------------------------------------------------------------------------------------------------
-- For logout state
---------------------------------------------------------------------------------------------------
IF (#EventID=6)
BEGIN
DECLARE #SilkQuantity INT=1 -- Quantity of silk to be given within the specified period.
DECLARE #ReqTime INT=60 -- The minimum required online period in minutes to be awarded for the silk reward.
UPDATE LOG_CharInOut SET Is_Online=0, Out_Date=GETDATE() WHERE CharID=#CharID
DECLARE #JID INT=(SELECT UserJID FROM SRO_VT_SHARD.dbo._User WITH (NOLOCK) WHERE CharID=#CharID)
DECLARE #LastOnlineTime INT=(SELECT DATEDIFF(MINUTE,(SELECT In_Date FROM LOG_CharInOut WHERE CharID=#CharID),(SELECT Out_Date FROM LOG_CharInOut WHERE CharID=#CharID)))
UPDATE LOG_CharInOut SET Last_OnlineTime=#LastOnlineTime, Total_OnlineTime=Total_OnlineTime+#LastOnlineTime WHERE CharID=#CharID
DECLARE #TotalOnlineTime INT, #UsedOnlineTime INT;
SELECT #TotalOnlineTime=Total_OnlineTime , #UsedOnlineTime=Used_OnlineTime FROM LOG_CharInOut WHERE CharID=#CharID
IF NOT EXISTS(SELECT JID FROM SRO_VT_ACCOUNT..SK_Silk WHERE JID=#JID)
BEGIN
INSERT INTO SRO_VT_ACCOUNT..SK_Silk (JID, silk_own, silk_gift, silk_point) VALUES(#JID, 0, 0, 0);
END
IF EXISTS(SELECT JID FROM SRO_VT_ACCOUNT..SK_Silk WHERE JID=#JID)
BEGIN
IF ((CONVERT(INT,#TotalOnlineTime-#UsedOnlineTime)/#ReqTime)>0)
BEGIN
UPDATE SRO_VT_ACCOUNT..SK_Silk SET silk_point=silk_point+(CONVERT(INT,((#TotalOnlineTime-#UsedOnlineTime)/#ReqTime))*#SilkQuantity) WHERE JID=#JID
UPDATE LOG_CharInOut SET Used_OnlineTime=Used_OnlineTime+((CONVERT(INT,((#TotalOnlineTime-#UsedOnlineTime)/#ReqTime)))*#ReqTime) WHERE CharID=#CharID
END
END
END
COMMIT TRANSACTION TRAN_SilkPerPeriod
END TRY
BEGIN CATCH
SELECT
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage;
ROLLBACK TRANSACTION TRAN_SilkPerPeriod
END CATCH
END
----==========================================================================================================----
---------------------------------------------- STAT RESET ------------------------------------------------
IF(#EventID=6 AND EXISTS(SELECT CharID FROM SRO_VT_LOG..LOG_CharStat WHERE CharID=#CharID))
BEGIN
DECLARE #RebirthCountForStat INT=(SELECT RebirthCount FROM SRO_VT_LOG..LOG_CharRebirth WITH (NOLOCK) WHERE CharID=#CharID)
DECLARE #MaxLevel TINYINT=(SELECT MaxLevel FROM SRO_VT_SHARD.._Char WITH (NOLOCK) WHERE CharID=#CharID)
DECLARE #StatPoint SMALLINT, #RemainStatPoint SMALLINT
SET #StatPoint=
(CASE
WHEN #RebirthCountForStat IS NULL THEN #MaxLevel+19
WHEN #RebirthCountForStat <= 5 THEN #MaxLevel+(#RebirthCountForStat*6)+19
WHEN #RebirthCountForStat > 5 THEN #MaxLevel+49
ELSE #MaxLevel+19
END)
SET #RemainStatPoint = (#MaxLevel*3)-3
UPDATE SRO_VT_SHARD.._Char SET Strength=#StatPoint, Intellect=#StatPoint, RemainStatPoint=#RemainStatPoint WHERE CharID=#CharID
DELETE FROM SRO_VT_LOG..LOG_CharStat WHERE CharID=#CharID
END
----==========================================================================================================----
-------------------------------------------- REBIRTH SYSTEM ----------------------------------------------
IF(#EventID=6)
BEGIN
DECLARE #RebirthCount INT=(SELECT RebirthCount FROM SRO_VT_LOG..LOG_CharRebirth WHERE CharID=#CharID)
DECLARE #Is_Active TINYINT=(SELECT Is_Active FROM SRO_VT_LOG..LOG_CharRebirth WHERE CharID=#CharID)
IF(#Is_Active=1 AND #RebirthCount<=5)-- Rebirth Count Limitation-1
BEGIN
UPDATE SRO_VT_SHARD.._Char SET
CurLevel=1,
MaxLevel=1,
ExpOffset=0,
SExpOffset=0,
Strength=20+(#RebirthCount*6),
Intellect=20+(#RebirthCount*6),
RemainSkillPoint=0,
RemainStatPoint=0
WHERE SRO_VT_SHARD.._Char.CharID=#CharID
DELETE CS FROM SRO_VT_SHARD.._RefSkill RS INNER JOIN SRO_VT_SHARD.._CharSkill CS ON CS.CharID=#CharID AND RS.ID=CS.SkillID AND RS.ReqCommon_MasteryLevel1<=110 AND RS.ID NOT IN (1,70,40,2,8421,9354,9355,11162,9944,8419,8420,11526,10625)
UPDATE SRO_VT_SHARD.._CharSkillMastery SET [Level]=0 WHERE CharID=#CharID AND [Level]<=110
UPDATE SRO_VT_LOG..LOG_CharRebirth SET Is_Active=0 WHERE CharID=#CharID
END
END
----==========================================================================================================----
--################################################################################################################```
You should used both. Let's create a simple table to demonstrate why and answer few fundamental questions.
DROP TABLE IF EXISTS [dbo].[StackOverflow];
CREATE TABLE [dbo].[StackOverflow]
(
[StackID] TINYINT
);
Now, execute the following statements (together):
INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (104);
INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (256);
SELECT [StackID]
FROM [dbo].[StackOverflow];
You will get an error because the second insert is trying to insert value, which can not be stored in TINYINT type.
The ACID transaction has four properties defining it. The first is Atomacity:
An atomic transaction is a set of events that cannot be
separated from one another and must be handled as a single unit of
work.
Knowing the above, one can think that the engine must rollback the two inserts, but it will not. Why?
Because in the context of the SQL Server, there are four methods for controlling transactions:
auto-commit
implicit
explicit
batch-scoped
The default one is auto-commit:
Any single statement that changes data and executes by itself is
automatically an atomic transaction. Whether the change affects one
row or thousands of rows, it must complete successfully for each row
to be committed. You cannot manually rollback an auto-commit
transaction.
As a result - the above two inserts are two separate transactions where the first is committed and second not.
So, let's use implicit transaction applying BEGIN and COMMIT key words to defined the transaction body:
BEGIN TRANSACTION;
INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (105);
INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (256);
COMMIT TRANSACTION;
SELECT [StackID]
FROM [dbo].[StackOverflow];
So, one can thing that now the engine is going to rollback the two inserts, right? And of course - it will not. Why?
Because, when the XACT_ABORT IS OFF (which is the default):
When SET XACT_ABORT is OFF, in some cases only the Transact-SQL
statement that raised the error is rolled back and the transaction
continues processing.
and when it is ON:
.. if a Transact-SQL statement raises a run-time error, the entire
transaction is terminated and rolled back.
That's what we need and if you try the code below, you can check this:
SET XACT_ABORT ON;
BEGIN TRANSACTION;
INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (105);
INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (256);
COMMIT TRANSACTION;
SET XACT_ABORT OFF;
SELECT [StackID]
FROM [dbo].[StackOverflow];
So, is this enough? The answer is no - because:
Compile errors, such as syntax errors, are not affected by SET
XACT_ABORT.
Here the first statement is committed, the second - not.
SET XACT_ABORT ON;
BEGIN TRANSACTION;
INSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (106);
EXECUTE
('
InnnNSERT INTO [dbo].[StackOverflow] ([StackID])
VALUES (256);
');
COMMIT TRANSACTION;
SET XACT_ABORT OFF;
SELECT [StackID]
FROM [dbo].[StackOverflow];
The template I am using when CRUD are performed and I need to rollback some work in case of error is:
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRY;
BEGIN TRANSACTION;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN;
ROLLBACK TRANSACTION;
END;
THROW; -- or log error or something else
END CATCH;
SET NOCOUNT, XACT_ABORT OFF;
You can check the Transaction Locking and Row Versioning Guide for more details.

DELETE using WHILE LOOP in a TRANSACTION

I am trying to delete table which just has around 39K records, but for some reasons it is taking time(Around 1.5 minutes) even to delete a single record. How can I improve the performance of my delete operation. How can I ensure that log activity is not taking much time. Can I put the "DELETE" statement within a while loop and then open a transaction and commit it each time it successfully completes. Any other effective method is available?
[PrimaryKey] here has a "Clustered Index"
DECLARE #BatchCount INT;
SELECT #BatchCount = COUNT(1) FROM #DHDID
DECLARE #Counter INT = 1
WHILE( #Counter <= #BatchCount)
BEGIN
BEGIN TRANSACTION
DECLARE #ID INT;
SELECT #ID = DHDID FROM #DHDID WHERE ID = #Counter
DELETE FROM <MYTABLE> WHERE [PrimaryKey] = #ID
COMMIT TRANSACTION
SET #Counter = #Counter + 1
END
Based on your answer, you should do a set-based delete via a join. Try something like this:
Begin Tran
Delete m
From <MyTable> m
Inner Join DHDID d
on d.DHDID = m.[PrimaryKey]
-- error detection code here
If <an error occurred>
Rollback
Else
Commit
I would try creating index on the #DHDID table:
CREATE NONCLUSTERED INDEX [idx] ON [#DHDID] ([ID] ASC) INCLUDE ([DHDID])

Insertion transaction- rollback SQL

I am new to databases and i try to create a stored procedure that inserts data in tables that are in a many to many relation.If any part of the operation fails then it must try to recover as much as possible from the entire operation. For example, if one wants to create a record regarding publishers and books and succeeds creating the publisher but fails with the book, then it should roll back
the creation of the book, but not of the publisher.
My code looks like this:
BEGIN TRY
BEGIN TRANSACTION
DECLARE #serviciuKey int
DECLARE #specializareKey int
IF NOT EXISTS (SELECT denumire, moneda, pret FROM Serviciu where denumire=#denumire and moneda=#moneda and pret=#pret)
BEGIN
INSERT INTO Serviciu ( denumire, moneda, pret)
VALUES (#denumire, #moneda, #pret)
END
SET #serviciuKey=##IDENTITY
SAVE TRANSACTION savepoint
IF NOT EXISTS (SELECT denumire, descriere FROM Specializare where denumire=#denumire_spec AND descriere=#descriere)
BEGIN
INSERT INTO Specializare( denumire, descriere)
VALUES (#denumire_spec, #descriere)
END
SET #specializareKey=##IDENTITY
SAVE TRANSACTION savepoint
IF NOT EXISTS (SELECT * FROM Specializare_Serviciu where cod_specializare=#specializareKey and cod_serviciu=#serviciuKey)
BEGIN
INSERT INTO Specializare_Serviciu( cod_specializare, cod_serviciu)
VALUES (#specializareKey, #serviciuKey)
END
SAVE TRANSACTION savepoint
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##trancount > 0 ROLLBACK TRANSACTION savepoint
DECLARE #msg nvarchar(2048) = error_message()
RAISERROR (#msg, 16, 1)
RETURN 55555
END CATCH
When i execute the procedure, i have this error:
Msg 3931, Level 16, State 1, Procedure AddData0, Line 76
The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.
Also when i try to insert some data that already exists, it is inserted with another ID, which means that IF NOT EXIST statement is not working.
Any help, please?
Try this version:
DECLARE #serviciuKey int
DECLARE #specializareKey int
BEGIN TRY
BEGIN TRAN
IF NOT EXISTS (SELECT denumire, moneda, pret FROM Serviciu where denumire=#denumire and moneda=#moneda and pret=#pret)
BEGIN
INSERT INTO Serviciu ( denumire, moneda, pret)
VALUES (#denumire, #moneda, #pret)
END
SET #serviciuKey=scope_identity()
IF NOT EXISTS (SELECT denumire, descriere FROM Specializare where denumire=#denumire_spec AND descriere=#descriere)
BEGIN
INSERT INTO Specializare( denumire, descriere)
VALUES (#denumire_spec, #descriere)
END
SET #specializareKey=scope_identity()
IF NOT EXISTS (SELECT * FROM Specializare_Serviciu where cod_specializare=#specializareKey and cod_serviciu=#serviciuKey)
BEGIN
INSERT INTO Specializare_Serviciu( cod_specializare, cod_serviciu)
VALUES (#specializareKey, #serviciuKey)
END
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##trancount > 0
ROLLBACK TRAN
DECLARE #msg nvarchar(2048) = error_message()
RAISERROR (#msg, 16, 1)
RETURN 55555
END CATCH

Select a column from a row and update the column to another value

I am trying to write a stored procedure that reads a column in a particular row of a table, then updates that column with a new value. The orig. is returned.
I want it to lock the row from others till I am done. What is the process?
I have something like
CREATE PROCEDURE [dbo].[aptc_Prt_NextDocumentNumberGet]
(#_iFormatConfigID INT, #_oNextDocumentNumber FLOAT OUTPUT)
AS
BEGIN
DECLARE #FrameworkConfig XML
SET #_oNextDocumentNumber = - 1
DECLARE #NewNextDocumentID FLOAT
SELECT
#_oNextDocumentNumber = FrameworkConfig.value('(/Parameters/Parameter[#Name="NextDocNo.NextDocumentNumber"])[1]', 'float')
FROM
[ttcPrtFormatConfig] WITH (ROWLOCK)
WHERE
FormatConfigID = #_iFormatConfigID
-- Select the Next Doc num out of the xml field
-- increment appropriate control and set output
IF #_iFormatConfigID IS NOT NULL
BEGIN
-- set what will be the "next" doc number after we add this current txn
IF (ABS(#_oNextDocumentNumber - 99999999999999999) < 0.0001)
BEGIN
SELECT #NewNextDocumentID = 1
END
ELSE
BEGIN
SELECT #NewNextDocumentID = #_oNextDocumentNumber + 1
END
UPDATE [ttcPrtFormatConfig]
WITH (ROWLOCK)
SET FrameworkConfig.modify('
replace value of
(/Parameters/Parameter[#Name="NextDocNo.NextDocumentNumber"]/text())[1]
with sql:variable("#NewNextDocumentID")')
WHERE FormatConfigID = #_iFormatConfigID
END
END
This should get you close to what you want.
DECLARE #MyValue INT
--You need a transaction so that the scope of your lock is well defined
BEGIN TRANSACTION
BEGIN TRY
--Get the value you are interested in, This select will lock the row so other people will not even be able to read it until you are finished!!!!!
SELECT #MyValue = MyValue
FROM MyTable WITH (UPDLOCK HOLDLOCK)
WHERE MyValue = SomeValue
--Do your checks and updates. You can take as long as you like as you are the only person who can do a read or update of this data.
IF
BEGIN
UPDATE MyTable
END
--Make sure you commit or rollback! this will release the lock
END TRY
BEGIN CATCH
--Oh no bad stuff! give up and put it back to how it was
PRINT ERROR_MESSAGE() + N' Your message here'
--Check there is a transaction that we can rollback
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK;
END
--You may want to return some error state and not throw!
THROW;
--RETURN -1 --(for example)
END CATCH;
--yay it all worked and your lock will be released
COMMIT
--Do what you like with the old value
RETURN #MyValue

Rollback the inner transaction of nested transaction

suppose I have following sql statement in sql server 2008:
BEGIN TRANSACTION
SqlStatement1
EXEC sp1
SqlStatement3
COMMIT TRANSACTION
The code of sp1
BEGIN TRANSACTION
SqlStatement2
ROLLBACK TRANSACTION
My question is: Is SqlStatement3 actually executed?
SQL Server doesn't really support nested transactions. There is only one transaction at a time.
This one transaction has a basic nested transaction counter, ##TRANCOUNT. Each consecutive begin transaction increments the counter by one, each commit transaction reduces it by one. Only the commit that reduces the counter to 0 really commits the one transaction.
A rollback transaction undoes the one transaction and clears ##TRANCOUNT.
In your case, the funny result is that SqlStatement3 is run outside a transaction! Your final commit will throw an "The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION" exception, but the effects of SqlStatement3 are permanent.
For example:
create table #t (col1 int)
insert #t (col1) values (1)
BEGIN TRANSACTION
update #t set col1 = 2 -- This gets rolled back
BEGIN TRANSACTION
update #t set col1 = 3 -- This gets rolled back too
ROLLBACK TRANSACTION
update #t set col1 = 4 -- This is run OUTSIDE a transaction!
COMMIT TRANSACTION -- Throws error
select col1 from #t
Prints 4. Really. :)
You can use transaction savepoints. sp1 can use a pattern like the one described in Error Handling and Nested Transactions:
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare #trancount int;
set #trancount = ##trancount;
begin try
if #trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
lbexit:
if #trancount = 0
commit;
end try
begin catch
declare #error int, #message varchar(4000), #xstate int;
select #error = ERROR_NUMBER(), #message = ERROR_MESSAGE(), #xstate = XACT_STATE();
if #xstate = -1
rollback;
if #xstate = 1 and #trancount = 0
rollback
if #xstate = 1 and #trancount > 0
rollback transaction usp_my_procedure_name;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
end catch
end
Such a pattern allow for the work done in sp1 to rollback, but keep the encompassing transaction active.
Nested transactions can be used. To only rollback the inner transaction, use a savepoint and rollback to the savepoint. In case the inner transaction does not whether it is nested or not, IF statements can be used to find out whether to set a savepoint and whether to rollback or to rollback to a savepoint:
BEGIN TRAN
DECLARE #WILL_BE_NESTED_TRANSACTION BIT = CASE WHEN (##TRANCOUNT > 0) THEN 1 ELSE 0 END
IF #WILL_BE_NESTED_TRANSACTION = 1
SAVE TRAN tran_save
BEGIN TRAN
-- do stuff
IF #WILL_BE_NESTED_TRANSACTION = 1
ROLLBACK TRAN tran_save
ELSE
ROLLBACK
ROLLBACK
Rollback transaction on its own rolls back all transactions.
http://msdn.microsoft.com/en-us/library/ms181299(v=sql.100).aspx
The statement will still be executed - try this
create table #t (i int)
insert #t values (1) -- t contains (1)
begin tran
update #t set i = i +1
select * from #t -- t contains (2)
begin tran
update #t set i = i +1
select * from #t -- t contains (3)
rollback tran -- transaction is rolled back
select * from #t -- t contains (1)
update #t set i = i +1
select * from #t -- t contains (2)
commit -- error occurs
select * from #t -- t contains (2)
drop table #t
Committing inner transactions is ignored by the SQL Server Database Engine. The transaction is either committed or rolled back based on the action taken at the end of the outermost transaction. If the outer transaction is committed, the inner nested transactions are also committed. If the outer transaction is rolled back, then all inner transactions are also rolled back, regardless of whether or not the inner transactions were individually committed.
Nesting Transactions in Microsoft TechNet

Resources