Spec for the stored procedure is:
To select and return the Id from my table tb_r12028dxi_SandpitConsoleProofClient (order is not important just the top 1 found will do) and as soon as I've selected that record it needs to be marked 'P' so that it does not get selected again.
Here is the stored procedure:
ALTER PROCEDURE [dbo].[r12028dxi_SandpitConsoleProofSweep]
#myId INT OUTPUT
AS
/*
DECLARE #X INT
EXECUTE [xxx].[dbo].[r12028dxi_SandpitConsoleProofSweep] #X OUTPUT
SELECT #X
*/
DECLARE #NumQueue INT = (
SELECT [cnt] = COUNT(*)
FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient
WHERE [Status] IS NULL
);
IF #NumQueue > 0
BEGIN
BEGIN TRANSACTION;
DECLARE #foundID INT = (SELECT TOP 1 Id FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient WHERE [Status] IS NULL);
UPDATE x
SET x.[Status] = 'P'
FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient x
WHERE x.Id = #foundID
SET #myId = #foundID;
RETURN;
COMMIT TRANSACTION;
END;
GO
It is returning the error message:
Transaction count after EXECUTE indicates a mismatching number of
BEGIN and COMMIT statements. Previous count = 0, current count = 1.
I've just added the Update script and the BEGIN TRANSACTION; and COMMIT TRANSACTION; before that it worked fine when it looked like the following...
ALTER PROCEDURE [dbo].[r12028dxi_SandpitConsoleProofSweep]
#myId INT OUTPUT
AS
/*
DECLARE #X INT
EXECUTE [xxx].[dbo].[r12028dxi_SandpitConsoleProofSweep] #X OUTPUT
SELECT #X
*/
DECLARE #NumQueue INT = (
SELECT [cnt] = COUNT(*)
FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient
WHERE [Status] IS NULL
);
IF #NumQueue > 0
BEGIN
SELECT TOP 1 #myId = Id FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient;
RETURN;
END;
GO
I added the BEGIN TRANSACTION; / COMMIT TRANSACTION; because I wanted to ensure that the data gets read into the output variable AND that the UPDATE happens. Should I just leave out this section of the procedure?
You have "RETURN;" before "COMMIT TRANSACTION;" which means "COMMIT TRANSACTION;" is never executed.
Give that you want:
and as soon as I've selected that record it needs to be marked 'P' so
that it does not get selected again.
you can achieve that in a single statment (and not in a transaction)
ALTER PROCEDURE [dbo].[r12028dxi_SandpitConsoleProofSweep]
#myId INT OUTPUT
AS
BEGIN
UPDATE x
SET x.[Status] = 'P',
#myID = x.ID
FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient x
/* a sample join to get your single row in an update statement */
WHERE x.ID = (SELECT MIN(ID)
FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient sub
WHERE ISNULL(sub.[Status], '') != 'P')
END
Note: When dealing with concurrent processing (ie: two threads trying to select from a single queue) it's more about the locking behavior than doing it inside a single transaction.
As an alternative to a perfectly reasonable suggestion by #Andrew Bickerton, you could also use a CTE and the ROW_NUMBER() function, like this:
WITH ranked AS (
SELECT
Id,
[Status],
rnk = ROW_NUMBER() OVER (ORDER BY Id)
FROM xxx.DBO.tb_r12028dxi_SandpitConsoleProofClient
WHERE [Status] IS NULL
)
UPDATE ranked
SET
[Status] = 'P',
#myId = Id
WHERE rnk = 1
;
The ROW_NUMBER() function assigns rankings to all rows where [Status] IS NULL, which allows you to update only a specific one.
The use of the CTE as the direct target of the UPDATE statement is absolutely legitimate in this case, as the CTE only pulls rows from one table. (This is similar to the use of views in UPDATE statements.)
Related
We have DVD Rental company. In this particular scenario we consider only Member, Rental and Membership tables.
The task is to write a trigger that prevents a customer from being shipped a DVD
if they have reached their monthly limit for DVD rentals as per their membership contract using the function.
My trigger leads to infinite loop. It works without While loop, but then it does not work properly, if I consider multiple updates to the Rental table. Where I am wrong?
-- do not run, infinite loop
CREATE OR ALTER TRIGGER trg_Rental_StopDvdShip
ON RENTAL
FOR UPDATE
AS
BEGIN
DECLARE #MemberId INT
DECLARE #RentalId INT
SELECT * INTO #TempTable FROM inserted
WHILE (EXISTS (SELECT RentalId FROM #TempTable))
BEGIN
IF UPDATE(RentalShippedDate)
BEGIN
IF (SELECT TotalDvdLeft FROM dvd_numb_left(#MemberId)) <= 0
BEGIN
ROLLBACK
RAISERROR ('YOU HAVE REACHED MONTHLY LIMIT FOR DVD RENTALS', 16, 1)
END;
END;
DELETE FROM #TempTable WHERE RentalID = #RentalId
END;
END;
My function looks as follows:
CREATE OR ALTER FUNCTION dvd_numb_left(#member_id INT)
RETURNS #tab_dvd_numb_left TABLE(MemberId INT, Name VARCHAR(50), TotalDvdLeft INT, AtTimeDvdLeft INT)
AS
BEGIN
DECLARE #name VARCHAR(50)
DECLARE #dvd_total_left INT
DECLARE #dvd_at_time_left INT
DECLARE #dvd_limit INT
DECLARE #dvd_rented INT
DECLARE #dvd_at_time INT
DECLARE #dvd_on_rent INT
SET #dvd_limit = (SELECT Membership.MembershipLimitPerMonth FROM Membership
WHERE Membership.MembershipId = (SELECT Member.MembershipId FROM Member WHERE Member.MemberId = #member_id))
SET #dvd_rented = (SELECT COUNT(Rental.MemberId) FROM Rental
WHERE CONCAT(month(Rental.RentalShippedDate), '.', year(Rental.RentalShippedDate)) = CONCAT(month(GETDATE()), '.', year(GETDATE())) AND Rental.MemberId = #member_id)
SET #dvd_at_time = (SELECT Membership.DVDAtTime FROM Membership
WHERE Membership.MembershipId = (SELECT Member.MembershipId FROM Member WHERE Member.MemberId = #member_id))
SET #dvd_on_rent = (SELECT COUNT(Rental.MemberId) FROM Rental
WHERE Rental.MemberId = #member_id AND Rental.RentalReturnedDate IS NULL)
SET #name = (SELECT CONCAT(Member.MemberFirstName, ' ', Member.MemberLastName) FROM Member WHERE Member.MemberId = #member_id)
SET #dvd_total_left = #dvd_limit - #dvd_rented
SET #dvd_at_time_left = #dvd_at_time - #dvd_on_rent
IF #dvd_total_left < 0
BEGIN
SET #dvd_total_left = 0
SET #dvd_at_time_left = 0
INSERT INTO #tab_dvd_numb_left(MemberId, Name, TotalDvdLeft, AtTimeDvdLeft)
VALUES(#member_id, #name, #dvd_total_left, #dvd_at_time_left)
RETURN;
END
INSERT INTO #tab_dvd_numb_left(MemberId, Name, TotalDvdLeft, AtTimeDvdLeft)
VALUES(#member_id, #name, #dvd_total_left, #dvd_at_time_left)
RETURN;
END;
Will be glad for any advice.
Your main issue is that even though you populate #TempTable you never pull any values from it.
CREATE OR ALTER TRIGGER trg_Rental_StopDvdShip
ON RENTAL
FOR UPDATE
AS
BEGIN
DECLARE #MemberId INT, #RentalId INT;
-- Move test for column update to the first test as it applies to the entire update, not per row.
IF UPDATE(RentalShippedDate)
BEGIN
SELECT * INTO #TempTable FROM inserted;
WHILE (EXISTS (SELECT RentalId FROM #TempTable))
BEGIN
-- Actually pull some information from #TempTable - this wasn't happening before
SELECT TOP 1 #RentalID = RentalId, #MemberId = MemberId FROM #TempTable;
-- Select our values to its working
-- SELECT #RentalID, #MemberId;
IF (SELECT TotalDvdLeft FROM dvd_numb_left(#MemberId)) <= 0
BEGIN
ROLLBACK
RAISERROR ('YOU HAVE REACHED MONTHLY LIMIT FOR DVD RENTALS', 16, 1)
END;
-- Delete the current handled row
DELETE FROM #TempTable WHERE RentalID = #RentalId
END;
-- For neatness I always drop temp tables, makes testing easier also
DROP TABLE #TempTable;
END;
END;
An easy way to debug simply triggers like this is to copy the T-SQL out and then create an #Inserted table variable e.g.
DECLARE #Inserted table (RentalId INT, MemberId INT);
INSERT INTO #Inserted (RentalId, MemberId)
VALUES (1, 1), (2, 2);
DECLARE #MemberId INT, #RentalId INT;
-- Move test for column update to the first test as it applies to the entire update, not per row.
-- IF UPDATE(RentalShippedDate)
BEGIN
SELECT * INTO #TempTable FROM #inserted;
WHILE (EXISTS (SELECT RentalId FROM #TempTable))
BEGIN
-- Actually pull some information from #TempTable - this wasn't happening before
SELECT TOP 1 #RentalID = RentalId, #MemberId = MemberId FROM #TempTable;
-- Select our values to its working
SELECT #RentalID, #MemberId;
-- IF (SELECT TotalDvdLeft FROM dvd_numb_left(#MemberId)) <= 0
-- BEGIN
-- ROLLBACK
-- RAISERROR ('YOU HAVE REACHED MONTHLY LIMIT FOR DVD RENTALS', 16, 1)
-- END;
-- Delete the current handled row
DELETE FROM #TempTable WHERE RentalID = #RentalId
END;
-- For neatness I always drop temp tables, makes testing easier also
DROP TABLE #TempTable;
END;
Note: throw is the recommended way to throw an error instead of raiserror.
Another thing to consider is that you must try to transform your UDF into an inline TVF because of some side effects.
Like this one:
CREATE OR ALTER FUNCTION dvd_numb_left(#member_id INT)
RETURNS TABLE
AS
RETURN
(
WITH
TM AS
(SELECT Membership.MembershipLimitPerMonth AS dvd_limit,
Membership.DVDAtTime AS dvd_at_time,
CONCAT(Member.MemberFirstName, ' ', Member.MemberLastName) AS [name]
FROM Membership AS MS
JOIN Member AS M
ON MS.MembershipId = M.MembershipId
WHERE M.MemberId = #member_id
),
TR AS
(SELECT COUNT(Rental.MemberId) AS dvd_rented
FROM Rental
WHERE YEAR(Rental.RentalShippedDate ) = YEAR(GETDATE)
AND MONTH(Rental.RentalShippedDate ) = MONTH(GETDATE)
AND Rental.MemberId = #member_id
)
SELECT MemberId, [Name],
CASE WHEN dvd_limit - dvd_rented < 0 THEN 0 ELSE dvd_limit - dvd_rented END AS TotalDvdLeft,
CASE WHEN dvd_limit - dvd_rented < 0 THEN 0 ELSE dvd_at_time - dvd_on_rent END AS AtTimeDvdLeft
FROM TM CROSS JOIN TR
);
GO
Which will be much more efficient.
The absolute rule to have performances is: TRY TO STAY IN A "SET BASED" CODE instead of iterative code.
The above function can be optimized by the optimzer whilet yours cannot and will needs 4 access to the same tables.
I have created my stored procedure, but I am confused how to set one column of from my table.
This is separate of my code:
CREATE PROC [dbo].[SP_Gabungan]
#REPORT_DT DATE
AS
BEGIN
DECLARE #action NVARCHAR(10),
#insCount INT = (SELECT COUNT (*) FROM INSERTED),
#delCount INT = (SELECT COUNT (*) FROM DELETED)
SELECT
#REPORT_DT AS REPORT_DATE,
FD.BRANCH_CODE AS [BRANCH],
#action AS [ID_OPERATIONAL], -- I want to set this value as 1(if there is a new input data, 2
-- (if there is updated data), 3 (if there is deleted data) from
-- from another field
BR.REGULATOR_BRANCH as [RG_BRANCH]
FROM
[DBO].[F_RR_FUNN] FD
LEFT JOIN
[DBO].[MS_BRANCH] BR ON BR.BRANCH_CD = FD.BRANCH_CODE
WHERE
FD.GROUP_PRODUCT = 'CA'
AND Y17sa = '1'
AND FD.REPORT_DATE = #REPORT_DT
END
How do I set column ID_OPERASIONAL as 1 (if there is a new data from another field), 2 for exists updated data from another field, 3 for deleted data from another field in a stored procedure.
ERROR from this code is:
Invalid object name 'INSERTED'
The problem the ERROR shows is that you cannot use deleted/inserted tables in stored procedures but just accessible in triggers.
If you want to have the count of inserted records or deleted records in a table there are two ways for doing this which the easiest one is:
Create you stored procedure like this:
CREATE PROC [dbo].[SP_Gabungan]
#REPORT_DT DATE,#DeletedCount INT , #InsertedCount Int
AS
BEGIN
...
Create a Trigger after insert and delete (so you can have inserted/deleted tables)
Then get the count just like you did in your code:
DECLARE #action nvarchar (10),
#insCount int = (SELECT COUNT (*) FROM INSERTED),
#delCount int = (SELECT COUNT (*) FROM DELETED)
Call your stored procedure in the Trigger and pass the #insCount and #delCount as inputs
EXEC [dbo].[SP_Gabungan]
#REPORT_DT = GETDATE() , #InsertedCount = #insCount , #DeletedCount = #delCount
A similar question is this for more other ways like temp tables or...
How use inserted\deleted table in stored procedure?
Also the link below is a question asking defining a trigger for both delete and insert so you can use both deleted/inserted tables together
SQL Trigger on Update, Insert, Delete on non-specific row, column, or table
Second way which is better when you are doing all these process a lot, is to get the log of your inserts or updates or deletes so you dont use triggers which reduce performance of your process.
(If usefull I can recommend some ideas for saving table logs)
CREATE PROC [dbo].[SP_Gabungan]
#REPORT_DT DATE
,#DeletedCount INT
,#InsertedCount INT
,#UpdateCount INT
AS BEGIN
DECLARE #action INT
SET #action = CASE
WHEN #InsertCount <> 0 THEN 1
WHEN #UpdateCount <> 0 THEN 2
WHEN #DeletedCount <> 0 THEN 3
END
SELECT
#REPORT_DT AS REPORT_DATE,
FD.BRANCH_CODE AS [BRANCH],
#action AS [ID_OPERATIONAL],
BR.REGULATOR_BRANCH as [RG_BRANCH]
FROM
[DBO].[F_RR_FUNN] FD
LEFT JOIN
[DBO].[MS_BRANCH] BR ON BR.BRANCH_CD = FD.BRANCH_CODE
WHERE
FD.GROUP_PRODUCT = 'CA'
AND Y17sa = '1'
AND FD.REPORT_DATE = #REPORT_DT END
CREATE TRIGGER [YourTriggerName]
AFTER INSERT/UPDATE/DELETE ON [db].[tablename]
FOR EACH ROW
BEGIN
DECLARE
#insCount int = (SELECT COUNT (*) FROM New), -- New in MySQL is same as inserted,deleted,updated
#delCount int = (SELECT COUNT (*) FROM Old),
#upCount int = (SELECT COUNT (*) FROM New),
EXEC [dbo].[SP_Gabungan]
#REPORT_DT = GETDATE()
,#DeletedCount = #delCount
,#InsertedCount = #insCount
,#UpdateCount = #upCount
END
Could you please help me to finish my trigger. What i got so far:
CREATE TRIGGER [dbo].[atbl_Sales_OrdersLines_ITrigGG]
ON [dbo].[atbl_Sales_OrdersLines]
FOR INSERT
AS
BEGIN
DECLARE #ID INT = (SELECT ProductID
FROM INSERTED)
DECLARE #OrderedQ INT = (SELECT SUM(Amount)
FROM atbl_Sales_OrdersLines
WHERE ProductID = #ID)
DECLARE #CurrentQ INT = (SELECT Quantity
FROM atbl_Sales_Products
WHERE ProductID = #ID)
DECLARE #PossibleQ INT = (SELECT Amount
FROM INSERTED
WHERE ProductID = #ID)
IF (#CurrentQ - #OrderedQ >= #PossibleQ)
ELSE
END
I need to complete the code. Can not figure out how to do it. I need that if condition is met - trigger would allow the insert. If else, trigger would stop the insert/or rollback and prompt a message that quantity is not sufficient.
Also, will this code work if insert is multiple lines with different product ids?
Thanks.
Something like this might work. This trigger checks the products that are in the insert, summing the total that have been ordered (now and in the past), and if any of them exceed the available quantity, the whole transaction is rolled back. Whenever writing triggers, you want to avoid any assumptions that there is a single row being inserted/updated/deleted, and avoid cursors. You want to just use basic set based operations.
CREATE TRIGGER [dbo].[atbl_Sales_OrdersLines_ITrigGG]
ON [dbo].[atbl_Sales_OrdersLines]
FOR INSERT
AS
BEGIN
IF (exists (select 1 from (
select x.ProductId, totalOrdersQty, ISNULL(asp.Quantity, 0) PossibleQty from (
select i.ProductId, sum(aso.Amount) totalOrdersQty
from (select distinct ProductId from inserted) i
join atbl_Sales_OrdersLines aso on aso.ProductId = i.ProductId
group by productId) x
left join atbl_Sales_Product asp on asp.ProductId = x.ProductId
) x
where PossibleQty < totalOrdersQty))
BEGIN
RAISERROR ('Quantity is not sufficient' ,10,1)
ROLLBACK TRANSACTION
END
END
I still think this is a horrible idea.
Try this,
CREATE TRIGGER [dbo].[atbl_Sales_OrdersLines_ITrigGG]
ON [dbo].[atbl_Sales_OrdersLines]
INSTEAD OF INSERT --FOR INSERT
AS
BEGIN
DECLARE #ID INT = (SELECT ProductID
FROM INSERTED)
DECLARE #OrderedQ INT = (SELECT SUM(Amount)
FROM atbl_Sales_OrdersLines
WHERE ProductID = #ID)
DECLARE #CurrentQ INT = (SELECT Quantity
FROM atbl_Sales_Products
WHERE ProductID = #ID)
DECLARE #PossibleQ INT = (SELECT Amount
FROM INSERTED
WHERE ProductID = #ID)
IF (#CurrentQ - #OrderedQ >= #PossibleQ)
BEGIN
INSERT INTO YOURTABLE (COLUMN1, COLUMN2, COLUMN3, ..)
SELECT COLUMN1, COLUMN2, COLUMN3, ..
FROM inserted
END
ELSE
BEGIN
RAISERROR ('Quantity is not sufficient' ,10,1)
ROLLBACK TRANSACTION
END
I am trying to Insert data in a table named "Candidate_Post_Info_Table_ChangeLogs" whenever a record is updated in another table named "Candidate_Personal_Info_Table". my code works fine whenever a single record is updated but when i try to updated multiple rows it gives error:
"Sub query returned more then 1 value".
Following is my code :
ALTER TRIGGER [dbo].[Candidate_PostInfo_UPDATE]
ON [dbo].[Candidate_Post_Info_Table]
AFTER UPDATE
AS
BEGIN
IF ##ROWCOUNT = 0
RETURN
DECLARE #Candidate_Post_ID int
DECLARE #Candidate_ID varchar(50)
DECLARE #Action VARCHAR(50)
DECLARE #OldValue VARCHAR(MAX)
DECLARE #NewValue VARCHAR(MAX)
DECLARE #Admin_id int
IF UPDATE(Verified)
BEGIN
SET #Action = 'Changed Verification Status'
SET #Candidate_Post_ID = (Select ID From inserted)
SET #Candidate_ID = (Select Identity_Number from inserted)
SET #NewValue = (Select Verified From inserted)
SET #OldValue = (Select Verified From deleted)
IF(#NewValue != #OldValue)
BEGIN
INSERT INTO Candidate_Post_Info_Table_ChangeLogs(Candidate_Post_ID, Candidate_ID, Change_DateTime, action, NewValue, OldValue, Admin_ID)
VALUES(#Candidate_Post_ID, #Candidate_ID, GETDATE(), #Action, #NewValue, #OldValue, '1')
END
END
END
i have searched stack overflow for this issue but couldn't get any related answer specific to this scenario.
When you insert/update multiple rows into a table, the Inserted temporary table used by the system holds all of the values from all of the rows that were inserted or updated.
Therefore, if you do an update to 6 rows, the Inserted table will also have 6 rows, and doing something like this:
SET #Candidate_Post_ID = (Select ID From inserted)
Will return an error, just the same as doing this:
SET #Candidate_Post_ID = (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6)
From the looks of things, you tried to do this with an iterative approach. Set-based is better. Maybe consider doing it like this in the body of your TRIGGER (without all of the parameters...):
IF UPDATE(Verified)
BEGIN
INSERT INTO Candidate_Post_Info_Table_ChangeLogs
(
Candidate_Post_ID
,Candidate_ID
,Change_DateTime
,action
,NewValue
,OldValue
,Admin_ID
)
SELECT
I.ID
,I.Identity_Number
,GETDATE()
,'Changed Verification Status'
,I.Verified
,O.Verified
,'1'
FROM Inserted I
INNER JOIN Deleted O
ON I.ID = O.ID -- Check this condition to make sure it's a unique join per row
WHERE I.Verified <> O.Verified
END
A similar case was solved in the following thread using cursors.... please check it
SQL Server A trigger to work on multiple row inserts
Also the below thread gives the solution based on set based approach
SQL Server - Rewrite trigger to avoid cursor based approach
*Both the above threads are from stack overflow...
I'm writing a stored procedure to update multiple records based on a table variable parameter.
The existing table is: Tb_Project_Image with relevant columns:
id PK (identity 1,1)
cat_ord decimal(4,2)
The procedure will receive a temporary table variable (shown in the code below) containing the id as PI_ID, and the new value for cat_ord as newCatOrd. idx is a simple identity for each row containing 1...n where n is the rowcount of #tempTable.
For each row in #tempTable, I want to update Tb_Project_Image where id = PI_ID to the corresponding value.
DECLARE #tempTable table (
idx smallint Primary Key IDENTITY(1,1),
PI_ID bigint,
newCatOrd decimal(4, 2) not null )
INSERT INTO #tempTable values (3, 7.01)
INSERT INTO #tempTable values (4, 7.02)
INSERT INTO #tempTable values (5, 7.03)
--etc...
DECLARE #error int
DECLARE #update int
DECLARE #iter int
SET #iter = 1
BEGIN TRAN
WHILE #iter <= (select COUNT(*) from #tempTable)
BEGIN
UPDATE Tb_Project_Image
SET cat_ord = (SELECT newCatOrd FROM #tempTable
WHERE idx = #iter)
WHERE id = (SELECT PI_ID FROM #tempTable
WHERE idx = #iter)
--error checking
set #error = ##ERROR
set #update = ##ROWCOUNT
IF ((#error = 0) AND (#update = 1))
BEGIN
SET #iter = #iter + 1
CONTINUE
END
ELSE
BREAK
END
IF ((#error = 0) AND (#update = 1))
COMMIT TRAN
ELSE
ROLLBACK TRAN
GO
Now, the error checking is because, to ensure integrity, EACH row in the temporary table MUST make 1 update. (explanation omitted to save space) If a single iteration of the while loop threw an error, or didn't effect exactly 1 row, I want to break the loop and rollback the transaction
THE PROBLEM I'm having is that this error checking is not working. I'm currently running it with 14 rows in #tempTable and the 11th uses a PI_ID not found in the Project_Image table. Therefore, #update = 0... but it continues the loop and commits the data.
I'd be doubly glad if someone had a method of doing this that only used a single update statement.
You cannot do it this way, because even SET resets the state of ##ERROR and ##ROWNUMBER variables. In this case ##ROWCOUNT is set to 1 after set #error = ##ERROR. If you do not assign the values to local variables, your code will work:
IF ((##error = 0) AND (##rowcount = 1))
But you might rather try try...catch error handling and test ##rowcount separately after update.
UPDATE: doing it in single update:
UPDATE t
SET cat_ord = tt.newCatOrd
FROM Tb_Project_Image t
INNER JOIN #tempTable tt
ON t.id = tt.PI_ID
-- If there was PI_ID not found in Tb_Project_Image
-- But I think that this should have been dealt with
-- During the initial loading of temporary table
IF ##ROWCOUNT <> (select count (*) from #tempTable)
BEGIN
-- Error reporting here
ROLLBACK TRANSACTION
END
Instead of updating and then rolling back, you could also use a CTE to determine if any records should be updated prior to performing the update. Something like this should work:
WITH NON_SINGLETON AS (
-- Find any records in #tempTable that don't match
-- exactly one record in Tb_Project_Image
SELECT t.PI_ID, COUNT(pi.id) C
FROM #tempTable t
LEFT JOIN Tb_Project_Image pi ON t.PI_ID = pi.id
GROUP BY t.PI_ID
HAVING COUNT(pi.id) != 1
)
UPDATE Tb_Project_Image
SET cat_ord = t.newCatOrd
FROM Tb_Project_Image pi
JOIN #tempTable t ON pi.id = t.PI_ID
-- If any invalid records were found in the CTE,
-- then this condition will fail for all rows
-- and nothing will be updated
WHERE NOT EXISTS(SELECT 1 FROM NON_SINGLETON)
If it's possible for #tempTable to have duplicate entries for the same PI_ID, then this will handle those scenarios as well. And since it's a single statement, you don't have to explicitly managing the transaction in the proc (if it's the only thing that needs to be included in the transaction).