Getting No of records inserted in Transaction - sql

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.

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.

Stored Procedure doesn't return affected rows after exception handling

I have added exception handling in my stored procedure as below.
ALTER PROCEDURE [dbo].[BUDGETUPDATE]
#DistrictID int
AS
BEGIN
SET NOCOUNT ON ;
BEGIN TRY
BEGIN TRAN
UPDATE bud
SET bud.BudgetStateID = #BudgetStateID
FROM [dbo].[BudgetOffice] bud
INNER JOIN [dbo].[vw_Office] vw
ON (vw.OfficeID = bud.OfficeID)
WHERE vw.DistrictID = #DistrictID
IF ##ERROR = 0
BEGIN
COMMIT TRAN;
SELECT ##ROWCOUNT AS AffectedRow;
END
END TRY
BEGIN CATCH
SELECT ##ERROR AS ERROR
ROLLBACK TRAN;
END CATCH
SET NOCOUNT OFF ;
END
I need to return the number of affected rows using ##ROWCOUNT. But this stored procedure always returns rowcount as 0. Any reason for this. Do I need to write the ##rowcount statement right after update?
You need to select ##ROWCOUNT after your UPDATE statement. As per the documentation:
Statements such as USE, SET , DEALLOCATE CURSOR, CLOSE CURSOR,
BEGIN TRANSACTION or COMMIT TRANSACTION reset the ROWCOUNT value to 0.
Since your ##ROWCOUNT is after the COMMIT TRAN, ##ROWCOUNT returns 0.
You need to store result of global variables in local variable because it will change after next instruction like:
ALTER PROCEDURE [dbo].[BUDGETUPDATE]
#DistrictID int
AS
BEGIN
SET NOCOUNT ON ;
DECLARE #rowcount INT, #error INT;
BEGIN TRY
BEGIN TRAN
UPDATE bud
SET bud.BudgetStateID = #BudgetStateID
FROM [dbo].[BudgetOffice] bud
JOIN [dbo].[vw_Office] vw
ON vw.OfficeID = bud.OfficeID
WHERE vw.DistrictID = #DistrictID;
SELECT #error = ##ERROR, #rowcount = ##ROWCOUNT;
IF #error = 0
BEGIN
COMMIT TRAN;
SELECT #rowcount AS AffectedRow;
END
END TRY
BEGIN CATCH
SELECT ##ERROR AS ERROR
ROLLBACK TRAN;
END CATCH
END
Or even better resign for using ##ERROR in TRY CATCH block:
ALTER PROCEDURE [dbo].[BUDGETUPDATE]
#DistrictID int
AS
BEGIN
SET NOCOUNT ON ;
BEGIN TRY
BEGIN TRAN
UPDATE bud
SET bud.BudgetStateID = #BudgetStateID
FROM [dbo].[BudgetOffice] bud
JOIN [dbo].[vw_Office] vw
ON vw.OfficeID = bud.OfficeID
WHERE vw.DistrictID = #DistrictID;
SELECT ##ROWCOUNT AS AffectedRow;
COMMIT TRAN;
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE() AS ERROR
ROLLBACK TRAN;
END CATCH
END
And where is #BudgetStateID defined?

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

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.

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.

What Order should I Call ##ROWCOUNT/##ERROR

I am inserting a number or rows into a table using INSERT with SELECT.
After the transaction, I want to store both the ##ROWCOUNT and ##ERROR values into locallay declared variables.
INSERT SubscriberList (PublicationId, SubscriberId)
SELECT #PublicationId, S.SubscriberId
FROM Subscribers S
SET #NoRows = ##ROWCOUNT
SET #ErrorCode = ##ERROR
I wasn't sure if this was valid in as much if I call one, will I negate the other?
Set them both at once:
SELECT #NoRows = ##ROWCOUNT, #ErrorCode = ##ERROR
In addition to #JNK's answer...
I never use ##ERROR now because of TRY/CATCH
BEGIN TRY
BEGIN TRAN
INSERT SubscriberList (PublicationId, SubscriberId)
SELECT #PublicationId, S.SubscriberId
FROM Subscribers S
SET #NoRows = ##ROWCOUNT
... do more inserts, updates etc
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
SET #ErrorCode = ERROR_NUMBER()
RAISERROR ...
END CATCH