Execute SP as many times as rows in a temp table? - sql

In my SP I have TVP type which include product information like,
Name Desc Visible Tags
This TVP include n rows. I am inserting this n rows in my Products table and putting the inserted Ids with Tags column in a temp table. After inserting, I need to execute my SP for each of the inserted product,
EXEC [InsertOrUpdateTags]
Means I need to execute this SP as many times as the number of rows in the temp table and passing the inserted Id. How can I do this? Here is my SP
ALTER PROCEDURE [dbo].[InsertOrUpdateTags]
(
#ProductId INT
,#Tags NVARCHAR(225)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #TranCount INT;
SET #TranCount = ##TRANCOUNT;
BEGIN TRY
IF #TranCount = 0
BEGIN TRANSACTION
ELSE
SAVE TRANSACTION InsertOrUpdateTags;
DECLARE #Tag TABLE(Name NVARCHAR(50));
DECLARE #TagIds TABLE(Id INT)
INSERT INTO #Tag
SELECT Items FROM dbo.Split(#Tags,',');
MERGE Tags AS D
USING (SELECT Name FROM #Tag) S
ON D.Name = S.Name
WHEN NOT MATCHED THEN
INSERT(Name)
VALUES(S.Name)
WHEN MATCHED THEN
UPDATE
SET Name = S.Name
OUTPUT INSERTED.ID INTO #TagIds;
-- Delete the one which was available before but not now
DELETE FROM ProductsTags WHERE BaseProductId = #ProductId AND TagId NOT IN (SELECT Id FROM #TagIds);
MERGE ProductsTags AS D
USING (SELECT Id FROM #TagIds) S
ON D.TagId = S.Id AND D.BaseProductId = #ProductId
WHEN NOT MATCHED THEN
INSERT(BaseProductId, TagId)
VALUES(#ProductId, S.Id);
LBEXIT:
IF #TranCount = 0
COMMIT;
END TRY
BEGIN CATCH
DECLARE #Error INT, #Message VARCHAR(4000), #XState INT;
SELECT #Error = ERROR_NUMBER() ,#Message = ERROR_MESSAGE() ,#XState = XACT_STATE();
IF #XState = -1
ROLLBACK;
IF #XState = 1 AND #TranCount = 0
rollback
IF #XState = 1 AND #TranCount > 0
ROLLBACK TRANSACTION InsertOrUpdateTags;
RAISERROR (' InsertOrUpdateTags: %d: %s', 16, 1, #error, #message) ;
END CATCH
END

You can do a CROSS APPLY with your Split to get all list of products with tag in a table variable and use the table variable in all your merge statements.
Something like this.
DECLARE #TempProduct TABLE(ProductID INTTagName VARCHAR(100))
INSERT INTO #TempProduct(ProductID,TagName)
SELECT ProductID,S.Items
FROM #TempTable CROSS APPLY dbo.Split(Tags,',') S;
MERGE Tags AS D
USING (SELECT DISTINCT TagName FROM #TempProduct) S
ON D.Name = S.Name
WHEN NOT MATCHED THEN
INSERT(Name)
VALUES(S.Name)
WHEN MATCHED THEN
UPDATE
SET Name = S.Name
OUTPUT INSERTED.ID,Inserted.Name INTO #TagIds(ID,Name);
-- Delete the one which was available before but not now
DELETE FROM ProductsTags
WHERE NOT EXISTS (SELECT 1 FROM #TempProduct TP INNER JOIN #TagIds TS ON TP.TagName = TS.TagName WHERE TP.ProductID = BaseProductId AND TS.ID= ProductsTags.TagId);
MERGE ProductsTags AS D
USING (SELECT TP.ProductID,TS.ID FROM #TempProduct TP INNER JOIN #TagIds TS ON TP.TagName = TS.TagName) S
ON D.TagId = S.ID AND D.BaseProductId = ProductID
WHEN NOT MATCHED THEN
INSERT(BaseProductId, TagId)
VALUES(ProductID, TagID);
LBEXIT:
IF #TranCount = 0
COMMIT;
END TRY
BEGIN CATCH
DECLARE #Error INT, #Message VARCHAR(4000), #XState INT;
SELECT #Error = ERROR_NUMBER() ,#Message = ERROR_MESSAGE() ,#XState = XACT_STATE();
IF #XState = -1
ROLLBACK;
IF #XState = 1 AND #TranCount = 0
rollback
IF #XState = 1 AND #TranCount > 0
ROLLBACK TRANSACTION InsertOrUpdateTags;
RAISERROR (' InsertOrUpdateTags: %d: %s', 16, 1, #error, #message) ;
END CATCH
END
Note: this was written here directly and might have some issues.
Hope this helps.

Related

SQL Server : while updating table, how come cannot delete is not updating row

ALTER PROCEDURE [dbo].[BG_CalcNetPT_Loop]
AS
BEGIN
SET NOCOUNT ON;
DECLARE #trancount int;
SET #trancount = ##trancount;
BEGIN TRY
IF #trancount = 0
begin transaction
ELSE
save transaction [BG_CalcNetPT_Loop];
SET NOCOUNT ON;
DECLARE #ErrorMessage NVARCHAR(255) = NULL;
DECLARE #Cursor as CURSOR;
DECLARE #UserId as int
DECLARE #TicketId as nvarchar(50) -- BDBG_IssueId
DECLARE #CreateDate as datetime
BEGIN
SET #Cursor = CURSOR FOR
-- select success bet which not yet calculate PT
select distinct BDBG_IssueId, BDBG_UserId from tbl_BettingDetails_BG a WITH (NOLOCK)
inner join tbl_Pt_BG b WITH (NOLOCK) on a.BDBG_IssueId = b.pt_GameID
where a.BDBG_Status = 1 and b.pt_isCalc = 0 and a.BDBG_Id = b.pt_TransID
and DATEDIFF (DAY, a.BDBG_CreatedDate, GETDATE()) <= 7 and pt_SourceID <> 1104
order by BDBG_IssueId desc
OPEN #Cursor
FETCH NEXT FROM #Cursor INTO
#TicketId
,#UserId
--,#CreateDate
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE
tbl_pt_BG
SET
tbl_pt_BG.pt_UplinePresetPT_or_FixPT = (SELECT CASE
WHEN (pt_fixpt > -1)
THEN pt_fixpt
WHEN (pt_UserType = 3) THEN
(SELECT isnull(BG_PTPercentage,0)
from tbl_UserDetails_BG WITH(NOLOCK) where BG_UserID = #UserID)
ELSE
pt_UplinePresetPT
END),
tbl_pt_BG.pt_downcomm2 = (SELECT CASE
WHEN pt_UserType = 3
THEN BG_RollingCommission
WHEN pt_usertype_lvl1 = 3
THEN BG_RollingCommission
ELSE
pt_downlinecomm
END)
FROM
tbl_pt_BG AS A WITH (NOLOCK)
--LEFT JOIN tbl_MemberFixPTSettingsBG AS B WITH (NOLOCK) ON A.pt_UserUplineID = B.AgentId --save in tbl_pt userid mean this agent take this user fix pt. CY 29/3/2020
LEFT JOIN tbl_UserDetails_BG C WITH(NOLOCK) ON A.pt_userID=c.BG_UserId
WHERE
pt_GameID = #TicketId and pt_SourceID = #UserID and pt_isCalc = 0
FETCH NEXT FROM #Cursor INTO
#TicketId
,#UserId
--,#CreateDate
END;
CLOSE #Cursor ;
DEALLOCATE #Cursor;
END;
lbexit:
if #trancount = 0
commit;
end try
begin catch
declare #error int, #message varchar(4000), #xstate int;
select #error = ERROR_NUMBER(),
#message = ERROR_MESSAGE(),
#xstate = XACT_STATE();
if #xstate = -1
rollback;
if #xstate = 1 and #trancount = 0
rollback
if #xstate = 1 and #trancount > 0
rollback transaction [BG_CalcNetPT_Loop];
SELECT CASE WHEN #ErrorMessage IS NULL THEN 'error_unknown_error' ELSE #ErrorMessage END AS errorMessage
--SELECT CASE WHEN #ErrorMessage IS NULL THEN ERROR_MESSAGE() ELSE #ErrorMessage END AS errorMessage
INSERT INTO tbl_debug
(
debug_sp,
debug_ticketid,
debug_text,
debug_createddate
)
VALUES
(
'API_BG_Order_Transfer',
CAST(#TicketId AS NVARCHAR(50)),
CAST(ERROR_MESSAGE() AS NVARCHAR(MAX)),
GETDATE()
)
raiserror ('API_BG_Order_Transfer: %d: %s', 16, 1, #error, #message) ;
end catch
END;
This code is a loop to update tbl_pt_dg where pt_sourceid <> 1104 while this loop is executing, I'm trying to run this delete query
delete from tbl_Pt_BG
where pt_GameID = '123'
and pt_isCalc = 1
and pt_SourceID = 1104
But this returns an error
Transaction (Process ID 70) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
Why while executing updating tbl_pt_bg is it locking the whole table? I can't wait for complete updating only delete the data.
Cursors and loops should be avoiding because they are terrible slow. You should try thinking in set operations instead of loops. It's not surprising that you get this error, until the UPDATE from inside the CURSOR finishes, the DELETE is blocked. All that work is wrapped inside a BEGIN TRANSACTION, until the COMMIT is done, the DELETE will be blocked.

SQL DB script performance tuning

I need to fix a prod DB issue and the clean up script I've is taking really long. I tried couple of things without any luck, following is the script:
DECLARE #ErrorMessage NVARCHAR(4000)
DECLARE #ErrorSeverity INT
DECLARE #ErrorState INT
DECLARE #ErrorProcedure NVARCHAR(50)
BEGIN TRY
IF OBJECT_ID('tempdb..#SuspectData') IS NOT NULL
BEGIN
DROP TABLE #SuspectData
END
CREATE TABLE #SuspectData
(
IID INT,
CID INT,
PID INT
)
INSERT INTO dbo.#SuspectData
SELECT DL.IID,DL.CID,IT.PID FROM DL
INNER JOIN IT ON IT.CID = DL.CID AND IT.IID = DL.IID
WHERE DL.Suspect = 1
WHILE (1 = 1)
BEGIN
BEGIN TRANSACTION
UPDATE TOP (5000) TDS
SET TDS.DTID = 4
FROM
TDS
INNER JOIN dbo.#SuspectData SD
ON TDS.IID = SD.IID AND TDS.PID = SD.PID
WHERE TDS.DTID <> 4
IF ##ROWCOUNT = 0
BEGIN
COMMIT TRANSACTION
BREAK
END
COMMIT TRANSACTION
END
WHILE (1 = 1)
BEGIN
BEGIN TRANSACTION
UPDATE TOP (5000) TDA
SET TDA.DTID = 4
FROM
TDA
INNER JOIN dbo.#SuspectData SD
ON TDA.IID = SD.IID AND TDA.PID = SD.PID
WHERE TDA.DTID <> 4
IF ##ROWCOUNT = 0
BEGIN
COMMIT TRANSACTION
BREAK
END
COMMIT TRANSACTION
END
DROP TABLE #SuspectData
END TRY
BEGIN CATCH
SELECT #ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorProcedure = ERROR_PROCEDURE()
RAISERROR (#ErrorMessage,#ErrorSeverity,#ErrorState,#ErrorProcedure) ;
END CATCH
I also have following script to update everything at same time but it is also taking really long time like 24 hours or something.
DECLARE #ErrorMessage NVARCHAR(4000)
DECLARE #ErrorSeverity INT
DECLARE #ErrorState INT
DECLARE #ErrorProcedure NVARCHAR(50)
BEGIN TRY
IF OBJECT_ID('tempdb..#SuspectData') IS NOT NULL
BEGIN
DROP TABLE #SuspectData
END
CREATE TABLE #SuspectData
(
IID INT,
CID INT,
PID INT
)
INSERT INTO dbo.#SuspectData
SELECT DL.IID,DL.CID,IT.PID FROM DL
INNER JOIN IT ON IT.CID = DL.CID AND IT.IID = DL.IID
WHERE DL.Suspect = 1
BEGIN TRANSACTION
--Update about 1.5M records
UPDATE TDS
SET TDS.DTID = 4
FROM
TDS
INNER JOIN dbo.#SuspectData SD
ON TDS.IID = SD.IID AND TDS.PID = SD.PID
WHERE TDS.DTID <> 4
COMMIT TRANSACTION
BEGIN TRANSACTION
--Update about 4.5M records
UPDATE TDA
SET TDA.DTID = 4
FROM
TDA
INNER JOIN dbo.#SuspectData SD
ON TDA.IID = SD.IID AND TDA.PID = SD.PID
WHERE TDA.DTID <> 4
COMMIT TRANSACTION
DROP TABLE #SuspectData
END TRY
BEGIN CATCH
SELECT #ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorProcedure = ERROR_PROCEDURE()
RAISERROR (#ErrorMessage,#ErrorSeverity,#ErrorState,#ErrorProcedure) ;
END CATCH
I'm guessing that TDS table is large. In that case you can speed up join operation between your temp table and TDS (ON TDS.IID = SD.IID AND TDS.PID = SD.PID) by creating index on your temporary table:
either primary clustered:
CREATE TABLE #SuspectData
(
IID INT,
CID INT,
PID INT,
CONSTRAINT pk_temp PRIMARY KEY(IID, PID)
)
or not-clustered (if IID-PID pairs are not unique):
CREATE INDEX IDX_Temp_SuspectData ON #SuspectData(IID,PID)
What you can also do is check execution plan of those queries - it will help you locate which operation takes so long.
On the side: I'm generally against using cursors if you can avoid it.
First, is there anything that changes DL.Suspect = 1 to something else? or does your data set just keep getting bigger?
I also agree with Sean Lange, does the update have to be all or nothing?
I would recommend using a cursor. cursors are a great way to break up large transaction to speed up use and reduce table locks.
DECLARE db_cursor CURSOR FOR SELECT DL.IID,DL.CID,IT.PID FROM DL
INNER JOIN IT ON IT.CID = DL.CID AND IT.IID = DL.IID
WHERE DL.Suspect = 1;
DECLARE #first INT;
DECLARE #second INT;
DECLARE #third INT;
OPEN db_cursor;
FETCH NEXT FROM db_cursor INTO #first , #second , #third ;
WHILE ##FETCH_STATUS = 0
BEGIN
-- Do your updates one row at a time here
UPDATE TDS
SET TDS.DTID = 4
FROM TDS
WHERE TDS.IID = #first AND TDS.PID = #third
WHERE TDS.DTID <> 4
END;
CLOSE db_cursor;
DEALLOCATE db_cursor;

Merge sql with date condition

I have create merge stored procedures as below, what i am trying to achieve is the following scenario:
Merge the new record if ProductTRN is not exist in ProductList table (complete)
Only Update the ProductList record in where the PU.CreateDate is bigger than CreateDate of target table which is ProductList (Not Complete)
Please advise me how I can achieve the second scenario above, thank you
CREATE PROCEDURE [dbo].[usp_ProductList_Merge]
AS
BEGIN
DECLARE #retValue INT
BEGIN TRY
IF OBJECT_ID('ProductList') IS NOT NULL
BEGIN
BEGIN TRANSACTION MergeConsumerTable
SET NOCOUNT ON;
MERGE dbo.ProductList AS target
USING
( SELECT
PU.ProductTRN,
PU.ProductName,
PU.ProductDescription,
PU.CreateDate
FROM dbo.TmpProductList PU
WHERE PU.ProductTRN = ProductTRN
) AS source (
ProductTRN,
ProductName,
ProductDescription
CreateDate)
ON ( (target.ProductTRN) = LOWER(source.ProductTRN)
)
WHEN MATCHED
THEN
UPDATE SET
ProductTRN= source.ProductTRN
WHEN NOT MATCHED
THEN
INSERT (
ProductTRN,
ProductName,
ProductDescription,
CreateDate
) VALUES
(
source.ProductTRN,
source.ProductName,
source.ProductDescription,
source.CreateDate,
);
DELETE PU
FROM dbo.TmpProductList PU
COMMIT TRANSACTION MergeProductListTable
SET #retValue = 1
SELECT #retValue
END
ELSE
BEGIN
SET #retValue = -1
SELECT #retValue
END
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION MergeProductListTable
DECLARE #ErrorMsg VARCHAR(MAX);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SET #ErrorMsg = ERROR_MESSAGE();
SET #ErrorSeverity = ERROR_SEVERITY();
SET #ErrorState = ERROR_STATE();
SET #retValue = 0
SELECT #retValue
-- SELECT 0 AS isSuccess
END CATCH
END
WITH Source AS (
SELECT ProductTRN
,ProductName
,ProductDescription
,CreateDate
FROM dbo.TmpProductList
)
MERGE ProductList AS Target
USING Source
ON Target.ProductTRN = Source.ProductTRN
WHEN MATCHED
AND Source.CreatedDate > Target.CreatedDate
THEN UPDATE SET
ProductName = Source.ProductName
,ProductDescription = Source.ProductDescription
,CreateDate = Source.CreatedDate
WHEN NOT MATCHED BY TARGET
THEN INSERT (
ProductTRN
,ProductName
,ProductDescription
,CreateDate
)
VALUES (
Source.ProductTRN
,Source.ProductName
,Source.ProductDescription
,Source.CreatedDate
)

Timeout occur sometimes when executing a Store Procedure

I have this stored procedure, which works if I execute manually. But sometimes I see timeout exception on running this Stored Procedure. Is this is due to MERGE, http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/
ALTER PROCEDURE [dbo].[InsertOrUpdateMobileUser]
(
#ID BIGINT
,#Name NVARCHAR(255)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #TranCount INT;
SET #TranCount = ##TRANCOUNT;
BEGIN TRY
IF #TranCount = 0
BEGIN TRANSACTION
ELSE
SAVE TRANSACTION InsertOrUpdateMobileUser;
MERGE INTO MobileUsers MU
USING (SELECT #ID AS ID) T ON (MU.ID = T.ID)
WHEN MATCHED THEN
UPDATE SET [Name] = CASE WHEN #Name IS NULL OR #Name = '' THEN [Name] ELSE #Name END
WHEN NOT MATCHED THEN
INSERT ([Name])
VALUES (#Name)
SELECT *
FROM MobileUsers
WHERE ID = #ID;
LBEXIT:
IF #TranCount = 0
COMMIT;
END TRY
BEGIN CATCH
DECLARE #Error INT, #Message VARCHAR(4000), #XState INT;
SELECT #Error = ERROR_NUMBER() ,#Message = ERROR_MESSAGE() ,#XState = XACT_STATE();
IF #XState = -1
ROLLBACK;
IF #XState = 1 AND #TranCount = 0
rollback
IF #XState = 1 AND #TranCount > 0
ROLLBACK TRANSACTION InsertOrUpdateMobileUser;
RAISERROR ('InsertOrUpdateMobileUser: %d: %s', 16, 1, #error, #message) ;
END CATCH
END
You differentiate between two execution methods. What are they, exactly? You mean that it works if run only the code of the procedure, and doesn't work when you EXECUTE the proc? Of it works through EXECUTE, and fails in a job?
Timeouts only concern client applications. You have SqlCommand .CommandTimeout property in .NET, and in Management Studio there's Tools>Options>Query Execution>Command Timeout. If you have a job, then it should run infinitely, there's even no option to set the timeout in Sql Server Agent.

Getting No of records inserted in Transaction

I need to get the no of records affected from the stored procedure. Based on this value I want to update the user whether the action got completed.
But my OUTPUT #RecordsAffected parameter is always 0
How to get the no of records inserted in the transaction?
Followed How can I get the number of records affected by a stored procedure? and http://rusanu.com/2009/06/11/exception-handling-and-nested-transactions/
This is my procedure
ALTER PROCEDURE [dbo].[SaveRoleFeatures]
(
#RoleId INT,
#SelectedFeatures AS IdInt READONLY,
#RecordsAffected INT OUTPUT
)
AS
BEGIN
set nocount on;
DECLARE #ErrorCode int
SELECT #ErrorCode = ##ERROR
declare #trancount int;
set #trancount = ##trancount;
BEGIN TRY
BEGIN TRAN
DELETE FROM RoleXFeature WHERE RoleIid= #RoleId
INSERT RoleXFeature
(
RoleIid,
FeatureId,
IsDeleted
)
SELECT
#RoleId,
Id,
0
FROM #SelectedFeatures
COMMIT TRAN
SET #RecordsAffected = ##ROWCOUNT
END TRY
BEGIN CATCH
declare #error int, #message varchar(4000), #xstate int;
select #error = ERROR_NUMBER(),
#message = ERROR_MESSAGE(), #xstate = XACT_STATE();
if #xstate = -1
rollback;
if #xstate = 1 and #trancount = 0
rollback
if #xstate = 1 and #trancount > 0
rollback transaction SaveRoleFeatures;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
return;
END CATCH
END
You need to check ##ROWCOUNT before you commit.
As #GSerg noted, this is because ##rowcount is supposed to return number of rows affected by the last statement, and commit is a statement that is documented to set ##rowcount to 0.