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.
Related
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
I am using SQL Server 2014 in which i had designed the database to work on the General store management.
I have to input the item along with its Supplier name its stock and Name of item. I have to execute three statements for this purpose, that is why I am using the transaction option as I need to roll back again if there is any type of problem during the implementation. Here is the SQL which I have been facing many issues with.
Here is the SQL code that I am trying to run:
BEGIN TRANSACTION AddItem
INSERT INTO Product(Name, Stock, Type_Id, Pur_Price, Sale_Price)
VALUES ('Lemon', 20, 2, 129, 325);
INSERT INTO Supplier(Name, Contact_No)
VALUES ('Kamran', '034637827');
INSERT INTO Purchase(Product_id, Supplier_Id, Quantity)
VALUES(EXEC spGetProductId #Name= 'Lemon', EXEC spGetSupplierId #Name='Kamran', 20);
COMMIT AddItem
ROLLBACK
Couple of issues:
Cannot use stored procedure output in a values clause.
Why do you have commit and rollback
Are you executing this from SSMS, some C# app etc?
what does sp_getproductid return resultset or return value?
Maybe something like this would work for SSMS or if you plan to make a stored procedure out of it
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO Product(Name, Stock, Type_Id, Pur_Price, Sale_Price)
VALUES ('Lemon', 20, 2, 129, 325);
INSERT INTO Supplier(Name, Contact_No)
VALUES ('Kamran', '034637827');
DECLARE #prodid int
EXEC #prodid = spGetProductId #Name= 'Lemon'
DECLARE #SupplierID int
EXEC #SupplierID = spGetSupplierId #Name='Kamran'
INSERT INTO Purchase(Product_id, Supplier_Id, Quantity)
VALUES(#prodid , #SupplierID, 20);
COMMIT
END TRY
BEGIN CATCH
DECLARE #ErrCode INT = 0,
#ErrMsg VARCHAR(4000) = '',
#CRLF CHAR(2) = CHAR(13) + CHAR(10)
SET #ErrCode = ##ERROR;
SET #ErrMsg = ERROR_MESSAGE();
IF XACT_STATE() = -1
ROLLBACK TRANSACTION;
ELSE IF ##TRANCOUNT = 1
ROLLBACK TRANSACTION;
ELSE IF ##TRANCOUNT > 1 AND XACT_STATE() = 1
COMMIT;
RAISERROR(#ErrMsg, 16, 1);
END CATCH
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
I'm experiencing some problems that look a LOT like a transaction in a stored procedure has been rolled back, even though I'm fairly certain that it was committed, since the output variable isn't set until after the commit, and the user gets the value of the output variable (I know, because they print it out and I also set up a log table where i input the value of the output variable).
In theory someone COULD manually delete and update the data such that it would look like a rollback, but it is extremely unlikely.
So, I'm hoping someone can spot some kind of structural mistake in my stored procedure. Meet BOB:
CREATE procedure [dbo].[BOB] (#output_id int OUTPUT, #output_msg varchar(255) OUTPUT)
as
BEGIN
SET NOCOUNT ON
DECLARE #id int
DECLARE #record_id int
SET #output_id = 1
-- some preliminary if-statements that doesn't alter any data, but might do a RETURN
SET XACT_ABORT ON
BEGIN TRANSACTION
BEGIN TRY
--insert into table A
SET #id = SCOPE_IDENTITY()
--update table B
DECLARE csr cursor local FOR
SELECT [some stuff] and record_id
FROM temp_table_that_is_not_actually_a_temporary_table
open csr
fetch next from csr into [some variables], #record_id
while ##fetch_status=0
begin
--check type of item + if valid
IF (something)
BEGIN
SET SOME VARIABLE
END
ELSE
BEGIN
ROLLBACK TRANSACTION
SET #output_msg = 'item does not exist'
SET #output_id = 0
RETURN
END
--update table C
--update table D
--insert into table E
--execute some other stored procedure (without transactions)
if (something)
begin
--insert into table F
--update table C again
end
DELETE FROM temp_table_that_is_not_actually_a_temporary_table WHERE record_id=#record_id
fetch next from csr into [some variables], #record_id
end
close csr
deallocate csr
COMMIT TRANSACTION
SET #output_msg = 'ok'
SET #output_id = #id
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
SET #output_msg = 'transaction failed !'
SET #output_id = 0
INSERT INTO errors (record_time, sp_name, sp_msg, error_msg)
VALUES (getdate(), 'BOB', #output_msg, error_message())
END CATCH
RETURN
END
I know, my user gets an #output_id that is the SCOPE_IDENTITY() and he also gets an #output_msg that says 'ok'. Is there ANY way he can get those outputs without the transaction getting committed?
Thank you.
You know the problem is that transaction dose NOT support rollback on variables because there is no data change inside database. Either commit or rollback of the transactions ONLY make difference on those database objects (tables, temp table, etc.), NOT THE VARIABLES (including table variables).
--EDIT
declare #v1 int = 0, #v2 int = 0, #v3 int = 0
set #v2 = 1
begin tran
set #v1 = 1
commit tran
begin tran
set #v3 = 1
rollback tran
select #v1 as v1, #v2 as v2, #v3 as v3
RESULT is as follows
Personally I never used transactions in stored procedures, especially when they are used simultaniously by many people. I seriously avoid cursors as well.
I think I would go with passing the involved rows of temp_table_that_is_not_actually_a_temporary_table into a real temp table and then go with an if statement for all rows together. That's so simple in tsql:
select (data) into #temp from (normal_table) where (conditions).
What's the point of checking each row, doing the job and then rollback the whole thing if say the last row doesn't meet the condition? Do the check for all of them at once, do the job for all of them at once. That's what sql is all about.
My ASP pages store session variables in SQL Server with the following stored procedure:
CREATE PROCEDURE [dbo].[MyProcedure]
#sessionId varchar(512),
#variable varchar(350),
#value image
AS
BEGIN
BEGIN TRAN
DECLARE #result int = 0;
DECLARE #locked bit;
IF (SELECT COUNT(*) FROM Sessions WHERE id = #sessionId) = 0
BEGIN
SET #result = -1;
END
ELSE BEGIN
DELETE Variables WHERE sessionId = #sessionId AND variable = #variable
IF #value IS NOT NULL
BEGIN
INSERT Variables VALUES(#sessionId, #variable, #value, 0)
END
END
COMMIT TRAN
RETURN #result
END
But once in a while, I get a primary key exception (Msg 2627): "Violation of PRIMARY KEY constraint 'PK_Variables'. Cannot insert duplicate key in object 'dbo.Variables'".
Note: There are no triggers involved.
Thanks!
Assuming your PK is on sessionId,variable then concurrent executions of the stored procedure with the same #sessionId,#variable could do this.
Both execute the
DELETE Variables WHERE sessionId = #sessionId AND variable = #variable
line concurrently and then both proceed to the insert.
This could only occur if there is no pre-existing record with the sessionId,variable combination as then the DELETEs would block.