tsql mutiple inserts in a transaction with rollback - sql-server

I have a stored procedure when exec it, it will insert into 2 tables several records.
let's say insert into author table a record and insert into book table several records(author's books).
how to make all insert in a transaction with rollback?
i read some articles/blogs, ##trancount/##error/XACT_STATE() makes me confused.
what's the best approach?
here's my proc:
CREATE PROCEDURE [dbo].[proc_addAuthors]
#bookid1 int, #bookid2 int, #bookid3 int, #bookid4 int, #bookid5 int,
#authInfo
AS
insert into author...(leave out params)
--get authorId
...
--insert books (leave out validation checks...)
insert into author2book(authorId, bookId) values(#authorid, #bookid1)
...
RETURN 0

If you just want to make sure that the inserts either both finishes succesfully, or both rolls back if an error occurs, you need to add the following to your stored procedure:
CREATE PROCEDURE [dbo].[proc_addAuthors]
#bookid1 int, #bookid2 int, #bookid3 int, #bookid4 int, #bookid5 int,
#authInfo
AS
SET XACT_ABORT ON; -- Automatically rollback if an error occurs.
BEGIN TRANSACTION;
insert into author...(leave out params)
--get authorId
...
--insert books (leave out validation checks...)
insert into author2book(authorId, bookId) values(#authorid, #bookid1)
...
COMMIT TRANSACTION;
RETURN 0

CREATE PROCEDURE addTitle(#title_id VARCHAR(6), #au_id VARCHAR(11),
#title VARCHAR(20), #title_type CHAR(12))
AS
BEGIN TRAN
INSERT titles(title_id, title, type)
VALUES (#title_id, #title, #title_type)
IF (##ERROR <> 0) GOTO ERR_HANDLER
INSERT titleauthor(au_id, title_id)
VALUES (#au_id, #title_id)
IF (##ERROR <> 0) GOTO ERR_HANDLER
COMMIT TRAN
RETURN 0
ERR_HANDLER:
PRINT 'Unexpected error occurred!'
ROLLBACK TRAN
RETURN 1

Here is the way to go with modern TRY CATCH block to test the error
CREATE PROCEDURE [dbo].[proc_addAuthors]
#bookid1 int, #bookid2 int, #bookid3 int, #bookid4 int, #bookid5 int,
#authInfo
AS
BEGIN TRY
BEGIN TRANSACTION
insert into author...(leave out params)
--get authorId
...
--insert books (leave out validation checks...)
insert into author2book(authorId, bookId) values(#authorid, #bookid1)
...
COMMIT TRANSACTION
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE() as errorMessage -- for example
ROLLBACK
RETURN 1 -- for example
END CATCH
RETURN 0

Using Try..Catch block gives you access to error functions, which you can use to get detailed information a about the error.
CREATE PROCEDURE [dbo].[proc_addAuthors]
#bookid1 int, #bookid2 int, #bookid3 int, #bookid4 int, #bookid5 int,
#authInfo
AS
BEGIN
SET NOCOUNT ON;
DECLARE #NewAuthorID INT;
BEGIN TRY
BEGIN TRANSACTION
insert into author...(leave out params)
--get authorId
SET #NewAuthorID = SCOPE_IDENTITY(); --<-- Get new AuthorID generated by Identity column
--insert books (leave out validation checks...)
insert into author2book(authorId, bookId) --<-- use that NewAuthorID param here
values(#NewAuthorID, #bookid1)
...
COMMIT TRANSACTION
RETURN 0
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
ROLLBACK TRANSACTION
DECLARE #ErrorNum int
SELECT #ErrorNum = ERROR_NUMBER()
SELECT ERROR_MESSAGE() AS ErrorMessage,
ERROR_LINE() AS ErrorLine,
--<--.... ( Other Error Functions )
RETURN #ErrorNum
END CATCH
END

Related

Stored procedure commit after DELETE and UPDATE run successfully

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

Rollback multiple transactions on try catch

Hi i'm learning transactions and i have difficulty trying to understand how do i use rollback when i have multiple transactions with different names. I'm using a try catch to call rollback in case an error occurs
CREATE PROCEDURE InsertFromPDP
AS
BEGIN
DECLARE #tempTable TABLE
(
Id INT PRIMARY KEY,
Referencia VARCHAR(15),
UAP NVARCHAR(20),
ConsumoInicialWeek01 FLOAT,
ConsumoInicialWeek02 FLOAT,
Stock INT,
PecasPorCaixa INT,
NumTurnos INT DEFAULT 3,
NumPab INT DEFAULT 6,
AlcanceAbastecimento INT DEFAULT 3,
QtdMin INT DEFAULT 4,
QtdMax INT DEFAULT 12,
NumDias INT DEFAULT 5
UNIQUE (Id)
)
INSERT INTO
#tempTable
(
Id,
Referencia,
UAP,
ConsumoInicialWeek01,
ConsumoInicialWeek02,
Stock,
PecasPorCaixa
)
SELECT
*
FROM
viewConsumoPDP
BEGIN TRY
BEGIN TRAN InsertNotExistsReferenciasFromPDP;
INSERT INTO
Parametros
SELECT
M.Referencia,
M.UAP,
M.NumTurnos,
M.NumPab,
M.AlcanceAbastecimento,
M.QtdMin,
M.QtdMax,
M.NumDias
FROM
#tempTable M
WHERE
NOT EXISTS
(
SELECT
*
FROM Parametros P
WHERE
M.Referencia <> P.Referencia
AND
M.UAP <> P.UAP
)
BEGIN TRAN InsertConsumoFromPDP
-- TODO--
COMMIT InsertNotExistsReferenciasFromPDP
COMMIT InsertConsumoFromPDP
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK InsertNotExistsReferenciasFromPDP
ROLLBACK InsertConsumoFromPDP
-- RAISE ERROR --
END CATCH
END
Is there a way to rollback all of the named transactions in just one command without specifying ROLLBACK + Name of the transaction?
ROLLBACK TRANSACTION without a savepoint_name or transaction_name rolls back to the beginning of the transaction.
You can find more details at: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/rollback-transaction-transact-sql?view=sql-server-2017

SQL TRY Error not CATCHING

Have a stored procedure and have wrapped the code in the format
BEGIN TRY
BEGIN TRAN
...code
COMMIT
END TRY
BEGIN CATCH
ROLLBACK
...code
END CATCH
Which works fine until I started doing some testing for various errors to make sure they were being entered into my log table correctly.
However, when I run this, it fails, and does not rollback and requires a manual rollback. It's like the code does not realise that it is in a TRY block.
Any ideas? (Code below, hoping it can be recreated on someone else's system and not some bizarre way on how the systems where I am are configured)
BEGIN
SET NOCOUNT ON
BEGIN TRY
BEGIN TRAN
---------------------------------------------------------------------
-- Create test table
---------------------------------------------------------------------
IF OBJECT_ID('tempdb..#DateOfBirth') IS NOT NULL DROP TABLE #DateOfBirth
CREATE TABLE #DateOfBirth
(
DateOfBirth DATE
)
INSERT INTO #DateOfBirth
VALUES
('1984-12-09')
,('1977-12-09')
,('2015-03-12')
,('1967-01-15')
---------------------------------------------------------------------
-- Date Of Birth
-- This Insert errors
---------------------------------------------------------------------
IF OBJECT_ID('tempdb..#DOB') IS NOT NULL DROP TABLE #DOB
CREATE TABLE #DOB
(
groupID INT IDENTITY(1,1)
, DateOfBirth INT -- Data Type mismatch
)
INSERT INTO #DOB
SELECT DateOfBirth
FROM #DateOfBirth
COMMIT
END TRY
BEGIN CATCH
PRINT 'Rollback'
ROLLBACK
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrorState INT = ERROR_STATE(),
#ErrorSeverity INT = ERROR_SEVERITY();
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState);
END CATCH
END
GO
Compilation errors within the same scope of the CATCH block cannot be caught. To wit, add a PRINT before each statement:
PRINT 'BATCH STARTED';
BEGIN
SET NOCOUNT ON
BEGIN TRY
PRINT 'BEGIN TRAN';
BEGIN TRAN;
IF OBJECT_ID('tempdb..#DateOfBirth') IS NOT NULL DROP TABLE #DateOfBirth;
PRINT 'CREATING #DateOfBirth';
CREATE TABLE #DateOfBirth
(
DateOfBirth DATE
);
PRINT 'INSERTING INTO #DateOfBirth';
INSERT INTO #DateOfBirth
VALUES
('1984-12-09')
,('1977-12-09')
,('2015-03-12')
,('1967-01-15')
IF OBJECT_ID('tempdb..#DOB') IS NOT NULL DROP TABLE #DOB;
PRINT 'CREATING #DOB';
CREATE TABLE #DOB
(
groupID INT IDENTITY(1,1)
, DateOfBirth INT -- Data Type mismatch
);
PRINT 'INSERTING INTO #DOB';
INSERT INTO #DOB
SELECT DateOfBirth
FROM #DateOfBirth;
PRINT 'COMMIT';
COMMIT;
END TRY
BEGIN CATCH
PRINT 'ROLLBACK';
ROLLBACK;
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrorState INT = ERROR_STATE(),
#ErrorSeverity INT = ERROR_SEVERITY();
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState);
END CATCH;
END;
GO
When this batch is initially executed on a new session, the resultant messages are:
BATCH STARTED
BEGIN TRAN
CREATING #DateOfBirth
INSERTING INTO #DateOfBirth
CREATING #DOB
INSERTING INTO #DOB
Msg 206, Level 16, State 2, Line 36
Operand type clash: date is incompatible with int
The important point is that this error occurs during statement compilation, not execution. Because the temp tables do not exist when the batch is compiled, compilation of the statements referencing those tables is deferred until the statements are run. Any errors that occur during the compilation can't be caught in by the CATCH block in the same scope.
The compilation error can be caught if you execute the statements using dynamic SQL so that the compilation occurs in a different scope:
PRINT 'BATCH STARTED';
BEGIN
SET NOCOUNT ON
BEGIN TRY
PRINT 'BEGIN TRAN';
BEGIN TRAN;
EXECUTE('
IF OBJECT_ID(''tempdb..#DateOfBirth'') IS NOT NULL DROP TABLE #DateOfBirth;
PRINT ''CREATING #DateOfBirth'';
CREATE TABLE #DateOfBirth
(
DateOfBirth DATE
);
PRINT ''INSERTING INTO #DateOfBirth'';
INSERT INTO #DateOfBirth
VALUES
(''1984-12-09'')
,(''1977-12-09'')
,(''2015-03-12'')
,(''1967-01-15'')
IF OBJECT_ID(''tempdb..#DOB'') IS NOT NULL DROP TABLE #DOB;
PRINT ''CREATING #DOB'';
CREATE TABLE #DOB
(
groupID INT IDENTITY(1,1)
, DateOfBirth INT -- Data Type mismatch
);
PRINT ''INSERTING INTO #DOB'';
INSERT INTO #DOB
SELECT DateOfBirth
FROM #DateOfBirth;
PRINT ''COMMIT'';
COMMIT;
');
END TRY
BEGIN CATCH
PRINT 'ROLLBACK';
ROLLBACK;
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrorState INT = ERROR_STATE(),
#ErrorSeverity INT = ERROR_SEVERITY();
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState);
END CATCH;
END;
GO
In this case, the CATCH block is entered and the transaction rolled back:
BATCH STARTED
BEGIN TRAN
CREATING #DateOfBirth
INSERTING INTO #DateOfBirth
CREATING #DOB
INSERTING INTO #DOB
ROLLBACK
Msg 50000, Level 16, State 2, Line 58
Operand type clash: date is incompatible with int
BTW, I strongly recommend you specify 'SET XACT_ABORT ON' to help ensure the transaction is rolled back after errors in cases where the CATCH block is not executed (e.g. a client query timeout error).
The error you're having is this:
Msg 206, Level 16, State 2, Line 39
Operand type clash: date is incompatible with int
For this type of error, it is suggested to handle in the application scope, since the batch is simply aborted.
I've compiled a few resources:
Book
Post on SQLSunday.com
Post from Brent Ozar & Team
Hope this helps you.

include exist in begin transaction

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

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