Stored procedure commit after DELETE and UPDATE run successfully - sql-server

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

Related

SQL Server - Changes not reflecting everytime until manually doing CHECKPOINT

I am having an issue in SQL Server procedure.
I have two new stored procedures, with the PROC_Main proc performing a bunch of inserts and updates before it calls the PROC_child to pull the updated records back out.
--Child PROC
CREATE PROCEDURE dbo.Proc_Child
#Id int
AS
BEGIN
SELECT * FROM dbo.Employee WHERE Id = #Id AND Status=1
END
--Parent Proc
CREATE PROCEDURE dbo.Proc_Main
#Id int ,#Status varchar(100),#Date datetime
AS
BEGIN
BEGIN TRY
BEGIN TRAN
IF NOT EXISTS (SELECT Id FROM dbo.Employee WHERE Id = #Id)
BEGIN
UPDATE dbo.Employee
SET Status = 3,
Date = getdate()
WHERE Status <> 3
AND Id = #Id
INSERT INTO dbo.Employee (ID,Status,Date)
VALUES (#ID,#Status,#Date)
END
COMMIT
--CHECKPOINT;
EXEC dbo.Proc_Child #Id = #Id
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN
DECLARE #Message VARCHAR(1000) = ERROR_MESSAGE()
DECLARE #Severity INT = ERROR_SEVERITY()
DECLARE #State INT = ERROR_STATE()
RAISERROR(#Message, #Severity, #State)
END CATCH
END
--Procedure call
EXEC Proc_Main #ID=1,#Status=1,#Date='2019-01-01'
I am facing the issue that Proc_Main is not returning the records from PROC_Child every time.
When I am manually doing checkpoint before Proc_Child is called then only it is returning records.
Nothing to do with checkpoint. Based on your code, if you call main proc with Status != 1, your child proc will not return it. Also, why are you doing update if you know that record does not exist? Finally, in the multi-threaded environment this may blow up, you need to lock the id when you checking for the existence.

committed inserted data not immediately available for select involving full text search

I have 2 stored procedures - simplified/pseudo code:
CREATE PROCEDURE [SomeSchema].[Sproc1]
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION X;
-- Insert lots of data
COMMIT TRANSACTION X;
END TRY
BEGIN CATCH
SELECT
#ErrorNumber = ERROR_NUMBER() ,
#ErrorSeverity = ERROR_SEVERITY() ,
#ErrorState = ERROR_STATE() ,
#ErrorProcedure = ERROR_PROCEDURE() ,
#ErrorLine = ERROR_LINE() ,
#ErrorMessage = ERROR_MESSAGE();
ROLLBACK TRANSACTION X;
END CATCH;
END;
CREATE PROCEDURE [SomeSchema].[Sproc2]
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION X;
-- Perform full text search on old and inserted data and return
COMMIT TRANSACTION X;
END TRY
BEGIN CATCH
SELECT
#ErrorNumber = ERROR_NUMBER() ,
#ErrorSeverity = ERROR_SEVERITY() ,
#ErrorState = ERROR_STATE() ,
#ErrorProcedure = ERROR_PROCEDURE() ,
#ErrorLine = ERROR_LINE() ,
#ErrorMessage = ERROR_MESSAGE();
ROLLBACK TRANSACTION X;
END CATCH;
END;
The first stored procedure Sproc1 inserts some data into several normalized tables. The second Sproc2 then selects data from the database using full text search. I run both stored procedures as follows:
EXEC [SomeSchema].[Sproc1]
EXEC [SomeSchema].[Sproc2]
Unfortunately data inserted via Sproc1 is not yet available when Sproc2 is run - only after about 1-3 seconds (guesstimate). What could be the reason for this? Should all this not be synchronous/atomic - i.e. the data should be available/selectable at the time Sproc2 executes?
Any suggestions to enforce that data insert/index is completed before Sproc2 is invoked would be very much appreciated. Thanks.
PS:
Just isolated the problem a sproc that is invoked inside Sproc2. This sproc uses sp_executesql and does not run inside a transaction. Not sure why this causes problems though ...
PPS:
It all seems to be related to full text search. This is part of my SSDT post-deployment script:
CREATE FULLTEXT CATALOG [SomeFullTextCatalog]
WITH ACCENT_SENSITIVITY = OFF
AS DEFAULT;
CREATE UNIQUE CLUSTERED INDEX ClusteredIndex_SomeView
ON [SomeSchema].[SomeView] (SomeId);
GO
CREATE FULLTEXT INDEX ON [SomeSchema].[SomeView ](
[Some1] LANGUAGE 'British English',
[Some2] LANGUAGE 'British English',
[Some3] LANGUAGE 'British English',
[Some4] LANGUAGE 'British English')
KEY INDEX [ClusteredIndex_SomeView] ON ([SomeFullTextCatalog], FILEGROUP [PRIMARY])
WITH (CHANGE_TRACKING = AUTO, STOPLIST = SYSTEM)
How can I 'refresh' this after an insert?
I can do:
I understand that I can do:
SELECT FULLTEXTCATALOGPROPERTY('SomeFullTextCatalog', 'PopulateStatus') AS Status
to check the status of the full text catalog and wait until its value is 0 again. Is this possible?
Do you need to recreated the full text index again, actually you need to do it once a day, to get all the inserted data for that day available.
The problem was that the full text index needs some time to refresh. To create a wait for this I have used this sproc taken from here:
CREATE PROCEDURE [Core].[USP_Core_WaitForFullTextIndexing]
#CatalogName VARCHAR(MAX)
AS
BEGIN
DECLARE #status int;
SET #status = 1;
DECLARE #waitLoops int;
SET #waitLoops = 0;
WHILE #status > 0 AND #waitLoops < 100
BEGIN
SELECT #status = FULLTEXTCATALOGPROPERTY(#CatalogName,'PopulateStatus')
FROM sys.fulltext_catalogs AS cat;
IF #status > 0
BEGIN
-- prevent thrashing
WAITFOR DELAY '00:00:00.1';
END
SET #waitLoops = #waitLoops + 1;
END
END

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

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.

Is this good writen transaction in stored procedure

This is the first time that I use transactions and I just wonder am I make this right. Should I change something?
I insert post(wisp). When insert post I need to generate ID in commentableEntity table and insert that ID in wisp table.
ALTER PROCEDURE [dbo].[sp_CreateWisp]
#m_UserId uniqueidentifier,
#m_WispTypeId int,
#m_CreatedOnDate datetime,
#m_PrivacyTypeId int,
#m_WispText nvarchar(200)
AS
BEGIN TRANSACTION
DECLARE #wispId int
INSERT INTO dbo.tbl_Wisps
(UserId,WispTypeId,CreatedOnDate,PrivacyTypeId,WispText)
VALUES
(#m_UserId,#m_WispTypeId,#m_CreatedOnDate,#m_PrivacyTypeId,#m_WispText)
if ##ERROR <> 0
BEGIN
ROLLBACK
RAISERROR ('Error in adding new wisp.', 16, 1)
RETURN
END
SELECT #wispId = SCOPE_IDENTITY()
INSERT INTO dbo.tbl_CommentableEntity
(ItemId)
VALUES
(#wispId)
if ##ERROR <> 0
BEGIN
ROLLBACK
RAISERROR ('Error in adding commentable entity.', 16, 1)
RETURN
END
DECLARE #ceid int
select #ceid = SCOPE_IDENTITY()
UPDATE dbo.tbl_Wisps SET CommentableEntityId = #ceid WHERE WispId = #wispId
if ##ERROR <> 0
BEGIN
ROLLBACK
RAISERROR ('Error in adding wisp commentable entity id.', 16, 1)
RETURN
END
COMMIT
Using try/catch based on #gbn answer:
ALTER PROCEDURE [dbo].[sp_CreateWisp]
#m_UserId uniqueidentifier,
#m_WispTypeId int,
#m_CreatedOnDate datetime,
#m_PrivacyTypeId int,
#m_WispText nvarchar(200)
AS
SET XACT_ABORT, NOCOUNT ON
DECLARE #starttrancount int
BEGIN TRY
SELECT #starttrancount = ##TRANCOUNT
IF #starttrancount = 0
BEGIN TRANSACTION
DECLARE #wispId int
INSERT INTO dbo.tbl_Wisps
(UserId,WispTypeId,CreatedOnDate,PrivacyTypeId,WispText)
VALUES
(#m_UserId,#m_WispTypeId,#m_CreatedOnDate,#m_PrivacyTypeId,#m_WispText)
SELECT #wispId = SCOPE_IDENTITY()
INSERT INTO dbo.tbl_CommentableEntity
(ItemId)
VALUES
(#wispId)
DECLARE #ceid int
select #ceid = SCOPE_IDENTITY()
UPDATE dbo.tbl_Wisps SET CommentableEntityId = #ceid WHERE WispId = #wispId
IF #starttrancount = 0
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 AND #starttrancount = 0
ROLLBACK TRANSACTION
RAISERROR ('Error in adding new wisp', 16, 1)
END CATCH
GO
You'd use TRY/CATCH since SQL Server 2005+
Your rollback goes into the CATCH block but your code looks good otherwise (using SCOPE_IDENTITY() etc). I'd also use SET XACT_ABORT, NOCOUNT ON
This is my template: Nested stored procedures containing TRY CATCH ROLLBACK pattern?
Edit:
This allows for nested transactions as per DeveloperX's answer
This template also allows for higher level transactions as per Randy's comment
i think its not good all the time ,but if you want to use more than one stored procedure same time its not good be cause each stored procedure handles the transaction independently
but in this case,you should use try catch block , for exception handling , and preventing keeping transaction open on when an exception raising
I've never considered it a good idea to put transactions in a stored procedure. I think it's much better to start a transaction at a higher level so that can better coordinate multiple database (e.g. stored procedure) calls and treat them all as a single transaction.

Resources