Using Transaction - sql-server

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.

Related

"The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION" I keep getting this error when I try to execute my stored procedure,Help me

ALTER PROCEDURE Add_Edit_Courses_new
#CourseCode VARCHAR,
... other params ...
AS
BEGIN TRY
DECLARE #ErrorCode INT =0, #ErrorMessage VARCHAR(25) = 'Action failed'
IF #TaskType > 2
BEGIN
RAISERROR('Wrong action key',16,1)
END
ELSE
BEGIN TRANSACTION
BEGIN
DECLARE #message VARCHAR(MAX)
IF #TaskType = 1
BEGIN
INSERT INTO Courses(...) VALUES(#CourseCode,...)
SET #message = 'Added Successfully'
END
ELSE IF #TaskType = 2
BEGIN
UPDATE Courses SET CourseCode=#CourseCode,...;
SET #message = 'Modified Successfully'
END
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
SELECT ERROR_NUMBER() AS ErrorNumber, ...
END CATCH
I wrote this stored procedure to insert and update and I used (1 & 2) to differentiate the task while using a try and catch, but every time I try to execute this stored procedure I keep getting that error, please can you help me with where I am wrong, I am just learning this principle for the first time.
Why is BEGIN TRANSACTION before BEGIN? I feel like BEGIN TRANSACTION/COMMIT TRANSACTION should be inside the ELSE conditional. With some noise removed:
IF #TaskType > 2
BEGIN
RAISERROR('Wrong action key',16,1);
END
ELSE
BEGIN -- moved this here
BEGIN TRANSACTION;
-- BEGIN -- removed this
DECLARE #message varchar(max);
IF #TaskType = 1
BEGIN
INSERT INTO Courses(...
SET #message = 'Added Successfully';
END
IF #TaskType = 2 -- don't really need ELSE there
BEGIN
UPDATE Courses SET ...
SET #message = 'Modified Successfully';
END
-- END -- removed this
COMMIT TRANSACTION;
SELECT #message;
END -- moved this here
In your catch you just blindly say:
ROLLBACK TRANSACTION;
This should be updated to:
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION;
END
Note that if you have a conditional where multiple statements are not wrapped correctly in BEGIN / END, they won't execute like you think. Consider:
IF 1 = 0
PRINT 'foo';
PRINT 'bar';
You get bar output every time, regardless of the result of the conditional.
You had something similar:
IF 1 = 1
-- do stuff
ELSE
BEGIN TRANSACTION;
BEGIN
-- do stuff
END
COMMIT TRANSACTION;
In that case, -- do stuff and the commit happened every time, even if the begin transaction did not, because that BEGIN/END wrapper (and anything that followed it) was not associated with the ELSE.
Perhaps a little dry and wordy if you're new to the topic, but Erland Sommarskog has a very comprehensive series on error handling here, might be worth a bookmark:
https://www.sommarskog.se/error_handling/Part1.html
https://www.sommarskog.se/error_handling/Part2.html
https://www.sommarskog.se/error_handling/Part3.html
Imo there are some major issues with this code. First, if the intention is for the transaction to be atomic then SET XACT_ABORT ON should be specified. Per the Docs RAISERROR does not honor SET XACT_ABORT ON so it could be converted to use THROW. Second, in cases where the ELSE block in the code is hit then COMMIT will always be hit (regardless of what happens to the commitable state of the transaction).
Also, the code performs the ROLLBACK before the code:
SELECT ERROR_NUMBER() AS ErrorNumber, ...
It's the ROLLBACK which clears the error messages and returns the state to normal. To catch the error metadata SELECT it before the ROLLBACK happens.

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

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

tsql mutiple inserts in a transaction with rollback

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

Resources