include exist in begin transaction - sql-server

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

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.

TRY CATCH inside of TRANSACTION

I have written a procedure which selects columns from a table and inserts into another table.
I put a try/catch block inside the transaction. If I got any error inbetween then my code will insert error details into error log table and roll back the transaction.
IF OBJECT_ID('usp_Stage_raw_ADDM_ALL') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[usp_Stage_raw_ADDM_ALL]
PRINT 'Procedure usp_Stage_raw_ADDM_ALL Dropped'
END
GO
CREATE PROC [dbo].[usp_Stage_raw_ADDM_ALL] AS
SET ANSI_NULLS ON
GO
SET NOCOUNT ON
GO
SET XACT_ABORT ON
GO
SET QUOTED_IDENTIFIER ON
GO
BEGIN TRANSACTION;
BEGIN TRY
DELETE FROM tbl_Staging_Devices WHERE [Source] LIKE 'ADDM%'
INSERT INTO tbl_Staging_Devices
(
Device_Name,
Serial_Number,
Company,
)
SELECT [Computer Name] AS Device_Name,
Serial_Number =
CASE
WHEN [Serial Number]='None' THEN NULL
ELSE [Serial Number]
END,
Company = 'Car'
FROM (
Select * From raw_ADDM_PCL
UNION ALL
Select * From raw_ADDM_CCL) a
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
EXEC USP_InsertErrorDetails
ROLLBACK TRANSACTION;
END CATCH
IF ##TRANCOUNT > 0
COMMIT TRANSACTION;
IF OBJECT_ID('usp_Stage_raw_ADDM_ALL') IS NOT NULL
BEGIN
PRINT 'Procedure usp_Stage_raw_ADDM_ALL Created'
END
GO
All that the sp you created does is
SET ANSI_NULLS ON
Your code is divided in batches using GO, and your SP's code is only that one:
CREATE PROC [dbo].[usp_Stage_raw_ADDM_ALL] AS
SET ANSI_NULLS ON
GO
You cannot insert "go" inside your procedure, "go" means the batch is finished, the code is submitted to server, and now you submit the CREATE PROC code with only 1 statement
Here is the solution.
CREATE PROC [dbo].[usp_Stage_raw_ADDM_ALL]
AS
BEGIN
SET ANSI_NULLS ON
SET NOCOUNT ON
SET QUOTED_IDENTIFIER ON
BEGIN TRY
BEGIN TRANSACTION
DELETE FROM tbl_Staging_Devices WHERE [Device_Name] LIKE 'ADDM%'
INSERT INTO tbl_Staging_Devices
(
Device_Name,
Serial_Number,
Company
)
SELECT [Computer Name] AS Device_Name,
Serial_Number =
CASE
WHEN [Serial Number]='None' THEN NULL
ELSE [Serial Number]
END,
Company = 'Car'
FROM (
Select * From raw_ADDM_PCL
UNION ALL
Select * From raw_ADDM_CCL) a
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
EXEC USP_InsertErrorDetails
ROLLBACK TRANSACTION;
END CATCH
END

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

try catch doesn't work correct T-SQL

I would like to ask why (try catch) doesn't work correct because when is error in person 2 trigger showed problem. Don`t ask about different between table person and table person 2 this is only example to show problem.
CREATE TRIGGER Example
ON Person
after INSERT
AS
BEGIN TRY
INSERT INTO [Person2]
SELECT
inserted.Name,
inserted.Surname,
inserted.Age,
inserted.Street
FROM inserted
END TRY
BEGIN CATCH
END CATCH
Insert working correct if trigger is delated
INSERT INTO Person
Values
('Jhon', 'Kowalsky', '12', null)
error
(0 row(s) affected)
Msg 3616, Level 16, State 1, Line 1 An error was raised during trigger
execution. The batch has been aborted and the user transaction, if
any, has been rolled back.
table Person - here you can see one difference between person and person 2 Street allow nulls
table Person 2 - here you can see one difference between person and person 2 Street doesn't allow nulls
Try this one -
CREATE TRIGGER dbo.Example
ON dbo.Person
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT ON
DECLARE #OwnTran BIT
SET #OwnTran = 0
IF ##TRANCOUNT = 0
BEGIN
SET #OwnTran = 1
BEGIN TRAN
END
BEGIN TRY
INSERT INTO dbo.Person2 (Name, Surname, Age, Street)
SELECT
i.Name
, i.Surname
, i.Age
, i.Street
FROM INSERTED i
END TRY
BEGIN CATCH
DECLARE #em NVARCHAR(MAX), #sev INT, #st INT
SELECT #em = ERROR_MESSAGE(),
#sev = ERROR_SEVERITY(),
#st = ERROR_STATE()
RAISERROR(#em, #sev, #st)
IF ##TRANCOUNT > 0
ROLLBACK TRAN
END CATCH
IF #OwnTran = 1 AND ##TRANCOUNT > 0
BEGIN
COMMIT TRAN
END
END

Using Transaction

How do I alter the following procedure in such a way that if insert statement is not successfully executed because of PrimaryKey or something the Delete Statement must also not be executed and further more it should generate an error message that I would Write myself.
CREATE PROCEDURE [dbo].[ReAdmissionInsert]
#GRNo varchar(4),
#ClassId numeric(2),
#FacultyId numeric(1),
#Section varchar(1),
#SessionId numeric(1)
AS
begin
insert into ReAdmissionDtl(GRNo,ClassId,FacultyId,Section,SessionId) values(#GRNo,#ClassId,#FacultyId,#Section,#SessionId)
delete from Discharge where GRNo = #GRNo
end
You use BEGIN TRAN & COMMIT to create a transaction that will be rolled back if your INSERT or DELETE fails:
CREATE PROCEDURE [dbo].[Readmissioninsert] #GRNo VARCHAR(4),
#ClassId NUMERIC(2),
#FacultyId NUMERIC(1),
#Section VARCHAR(1),
#SessionId NUMERIC(1)
AS
BEGIN
BEGIN TRAN --<= Starting point of transaction
INSERT INTO readmissiondtl
(grno,
classid,
facultyid,
section,
sessionid)
VALUES (#GRNo,
#ClassId,
#FacultyId,
#Section,
#SessionId)
DELETE FROM discharge
WHERE grno = #GRNo
COMMIT --<= End point of transaction
END
Documentation
You can use a TRY CATCH for the error message:
How to rollback a transaction in TSQL
TRY CATCH THROW: Error handling changes in T-SQL
Use a transaction and a try catch block. Raise your error in the catch block, like so:
CREATE PROCEDURE [dbo].[ReAdmissionInsert]
#GRNo varchar(4),
#ClassId numeric(2),
#FacultyId numeric(1),
#Section varchar(1),
#SessionId numeric(1)
AS
begin
Begin transaction
Begin try
insert into ReAdmissionDtl(GRNo,ClassId,FacultyId,Section,SessionId)
values(#GRNo,#ClassId,#FacultyId,#Section,#SessionId)
delete from Discharge where GRNo = #GRNo
Commit transaction
End try
Begin catch
Rollback
Raiserror(999999,'my message',16,1)
End catch
end
Best Practice for Writing SQL Server Stored Procedure with Transaction -
Enclose withing TRY..CATCH block
Check for ##TRANCOUNT and ROLLBACK Transaction on ERROR
RAISE actual ERROR to alarm calling program
Sample -
CREATE PROCEDURE [dbo].[ReAdmissionInsert]
#GRNo varchar(4),
#ClassId numeric(2),
#FacultyId numeric(1),
#Section varchar(1),
#SessionId numeric(1)
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
BEGIN TRAN
insert into ReAdmissionDtl(GRNo,ClassId,FacultyId,Section,SessionId)
values(#GRNo,#ClassId,#FacultyId,#Section,#SessionId)
delete from Discharge where GRNo = #GRNo
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN
/*ERROR OCCURED*/
DECLARE #ERROR_MESSAGE NVARCHAR(4000);--MESSAGE TEXT
DECLARE #ERROR_SEVERITY INT;--SEVERITY
DECLARE #ERROR_STATE INT;--STATE
SELECT #ERROR_MESSAGE = ERROR_MESSAGE(),
#ERROR_SEVERITY = ERROR_SEVERITY(),
#ERROR_STATE = ERROR_STATE()
/*RETURN ERROR INFORMATION ABOUT THE ORIGINAL ERROR THAT CAUSED
EXECUTION TO JUMP TO THE CATCH BLOCK.*/
RAISERROR (#ERROR_MESSAGE, #ERROR_SEVERITY, #ERROR_STATE)
END CATCH
END
Notice that ##TRANCOUNT is checked to verify if there is any open transactions and ERROR messages are retained and raised so program will receive SqlException.

Resources