SQL: Amount is not incremented during table looping - sql

I have to calculate an after tax salary amount based on a gross salary present in one table, and different other parameters present in another table. This is the situation:
I have a salary table that contains the gross salary of employees
To compute the net amount, I have to either substract or add other parameters (contributions, insurance, ...) based on whether the corresponding value has to be considered as either gross or relative (percentage). Here is the table:
Logic:
Relativite = 1 means that the value (valeur in the table) is percentage, 0 means it's gross.
Sens = 1 means the value has to be substracted from the salary, 0 means it has to be added.
With this example, what I want to achieve in order to get the net salary is something like this:
1st Line: Net_Salary = (700 - (700*13.4)/100).
2nd Line: Net Salary = value of first Line - 13
3rd LIne: Net Salary = value of 2nd Line - 13000 and so forth...
To achieve this, I have used a cursor that loops through the table and fetches each value to compute the net salary. I end up with something like this:
The problem with this result is that the amount is not decremented while looping through the table. It always computes based on the original value.
Here is the code I have used:
declare #registration_nr varchar(20),
#entity_id varchar(10)
DECLARE #gross_salary float, #net_salary float, #cursid int, #category varchar(50), #value float, #relative numeric(1), #sens numeric(1)
set #registration_nr = '19820506-0-2';
set #entity_id = 'edu7';
SET #gross_salary = (select pay_amount from dbo.EMPLOYEES_PAY where registration_nr = #registration_nr and entity_id = #entity_id and active = 1)
--set #rowcnt = (select count(1) from dbo.PARAMETRES_SALAIRES where code_institution = #entity_id and actif = 1)
CREATE TABLE #temp
(registration_nr varchar(20),
category varchar(50),
valeur float,
relativite numeric(1),
sens numeric(1),
salaire_net float);
DECLARE curs_rowid CURSOR FAST_FORWARD FOR
SELECT nom_categorie,
relativite,
valeur,
sens
FROM dbo.SALARY_SETTINGS --This is the table that contains the parameters (insurance,...)
WHERE code_institution = #entity_id and actif = 1;
OPEN curs_rowid
FETCH NEXT FROM curs_rowid INTO #category, #relative, #value, #sens
WHILE ##fetch_status = 0
BEGIN
if #relative = 0
BEGIN
if #sens = 0
BEGIN
set #net_salary = #gross_salary + (#gross_salary*#value)/100
INSERT INTO #temp (category, valeur, relativite, sens, salaire_net)
values(#category, #value, #relative, #sens, #net_salary);
END;
else if #sens = 1
BEGIN
set #net_salary = #gross_salary - (#gross_salary*#value)/100
INSERT INTO #temp (category, valeur, relativite, sens, salaire_net)
values(#category, #value, #relative, #sens, #net_salary);
END;
END;
else if #relative = 1
BEGIN
if #sens = 0
BEGIN
set #net_salary = #gross_salary + #value
INSERT INTO #temp (category, valeur, relativite, sens, salaire_net)
values(#category, #value, #relative, #sens, #net_salary);
END;
else if #sens = 1
BEGIN
set #net_salary = #gross_salary - #value
INSERT INTO #temp (category, valeur, relativite, sens, salaire_net)
values(#category, #value, #relative, #sens, #net_salary);
END;
END;
FETCH NEXT FROM curs_rowid INTO #category, #relative, #value, #sens
END;
CLOSE curs_rowid;
DEALLOCATE curs_rowid;
Any idea how I can solve this thing and have on the last row the last value that is based on all the previous calculations?

After the line:
SET #gross_salary = (select pay_amount from dbo.EMPLOYEES_PAY where registration_nr = #registration_nr and entity_id = #entity_id and active = 1)
Add
SET #net_salary=#gross_salary;
And in the cursor part, replace all #gross_salary with #net_salary

Related

Iterate inserted in trigger

This SQL Server trigger works correctly for a single insert
CREATE TRIGGER [dbo].[DebitSale]
ON [dbo].[OrderDetails]
AFTER INSERT, UPDATE
AS
DECLARE #OrderId bigint
DECLARE #OrderNr int
DECLARE #TaxRate decimal(9,2)
DECLARE #Tax decimal(9,2)
DECLARE #Total decimal(9,2)
DECLARE #syncRate decimal(9,8)
DECLARE #Net decimal(9,2)
DECLARE #OldNet decimal(9,2)
DECLARE #ExactFee decimal(9,2)
DECLARE #Fee decimal(9,2)
DECLARE #JobId bigint
DECLARE #ItemCode nvarchar(35)
IF EXISTS ( SELECT 0 FROM inserted )
BEGIN
SELECT #syncRate = SyncRate
FROM dbo.[UserAccount]
WHERE Id = 0
SELECT #Net = i.NrUnits * i.UnitNet from inserted i
SELECT #OldNet = d.NrUnits * d.UnitNet from deleted d
IF(#Net=#OldNet)
BEGIN
RETURN
END
SELECT #OrderId = i.OrderId, #JobId = i.JobId, #ItemCode = i.ItemCode FROM inserted i
SELECT #OrderNr = OrderNr, #TaxRate = TaxRate FROM Orders WHERE OrderId = #OrderId
SET #Tax = #Net * #TaxRate / 100
SET #Total = #Net + #Tax
SET #ExactFee = #Total * #syncRate
SET #Fee = FLOOR(#ExactFee * 100)/100
IF(#Fee >= 0.01)
BEGIN
IF #JobId IS NULL
INSERT INTO SubscriptionTransactions
(ItemValue, OrderNr, TransactionCredit, TransactionDate, TransactionDebit, TransactionDescription, TransactionType)
VALUES (#Total, #OrderNr, 0, GetDate(), #Fee, #ItemCode, 'Item')
ELSE
INSERT INTO SubscriptionTransactions
(ItemValue, OrderNr, TransactionCredit, TransactionDate, TransactionDebit, TransactionDescription, TransactionType)
VALUES (#Total, #OrderNr, 0, GetDate(), #Fee, #ItemCode, 'Job')
END
END
However we sometimes get multiple rows in inserted so I included a loop like this
ALTER TRIGGER [dbo].[DebitSale]
ON [dbo].[OrderDetails]
AFTER INSERT, UPDATE
AS
BEGIN
DECLARE #OrderId bigint
DECLARE #OrderDetailId bigint
DECLARE #OrderNr int
DECLARE #TaxRate decimal(9,2)
DECLARE #Tax decimal(9,2)
DECLARE #Total decimal(9,2)
DECLARE #syncRate decimal(9,8)
DECLARE #Net decimal(9,2)
DECLARE #OldNet decimal(9,2)
DECLARE #ExactFee decimal(9,2)
DECLARE #Fee decimal(9,2)
DECLARE #JobId bigint
DECLARE #ItemCode nvarchar(35)
SELECT #syncRate = SyncRate
FROM dbo.[UserAccount]
WHERE Id = 0
DECLARE #current INT = 0;
DECLARE #InsertedCount int
SELECT #InsertedCount = Count(*) FROM inserted
DECLARE #Thing nvarchar(max)
SELECT #Thing ='InsertedCount ' + CAST(#InsertedCount AS VARCHAR)
INSERT INTO DebugTable (DebugColumn) VALUES (#Thing)
WHILE #current <= #InsertedCount
BEGIN
SELECT #Thing ='current ' + CAST(#current AS VARCHAR)
INSERT INTO DebugTable (DebugColumn) VALUES (#Thing)
SELECT #OrderDetailId AS OrderDetailId from inserted i
ORDER BY OrderDetailId
OFFSET #current ROWS
FETCH NEXT 1 ROWS ONLY
SET #current = #current + 1;
SELECT #Net = NrUnits * UnitNet FROM inserted i WHERE i.OrderDetailId= #OrderDetailId
SELECT #OldNet = d.NrUnits * d.UnitNet from deleted d where d.OrderDetailId= #OrderDetailId
IF(#Net=#OldNet)
CONTINUE
SELECT #OrderId = i.OrderId, #JobId = i.JobId, #ItemCode = i.ItemCode FROM inserted i WHERE i.OrderDetailId= #OrderDetailId
SELECT #OrderNr = OrderNr, #TaxRate = TaxRate FROM Orders WHERE OrderId = #OrderId
SET #Tax = #Net * #TaxRate / 100
SET #Total = #Net + #Tax
SET #ExactFee = #Total * #syncRate
SET #Fee = FLOOR(#ExactFee * 100)/100
IF(#Fee < 0.01)
CONTINUE
IF (#JobId IS NULL)
INSERT INTO SubscriptionTransactions
(ItemValue, OrderNr, TransactionCredit, TransactionDate, TransactionDebit, TransactionDescription, TransactionType)
VALUES (#Total, #OrderNr, 0, GetDate(), #Fee, #ItemCode, 'Item')
ELSE
INSERT INTO SubscriptionTransactions
(ItemValue, OrderNr, TransactionCredit, TransactionDate, TransactionDebit, TransactionDescription, TransactionType)
VALUES (#Total, #OrderNr, 0, GetDate(), #Fee, #ItemCode, 'Job')
END
INSERT INTO DebugTable (DebugColumn) VALUES (#ItemCode)
END
This is a remote server that services a Web API. I don't have access to remote debugging so I've put some debug code in that logs a couple of lines to a DebugTable. The trigger writes to DebugTable if I execute an insert statement in SSMS, but doesn't update Transactions.
The Web API executes a merge statement in which case nothing is written to DebugTable and Transactions are not updated. I'm not sure if the new trigger is even firing.
I know there is a lot of TSQL here, but given the original trigger seems to work for a single insert, and that trigger is unchanged except for wrapping it in a loop, I assume I've got the loop wrong.
I expect some will spurn the loop, but in mitigation, there are rarely more than 2 rows inserted at a time.
How do I loop through inserted in a trigger?
Your main insert should be something like the following i.e. there is no need for a loop. One should always aim for set based operations in SQL if at all possible. The key points are:
JOIN the relevant tables together
use CROSS APPLY to avoid having to repeat calculations
use the WHERE clause to exclude cases you are currently using CONTINUE for.
I've tried to match your logic as best I can. But without being able to test it I can't be sure it works. You will want to run it through a few tests to be sure its working exactly as you wish.
And I leave the debugging/logging code as an exercise for you.
INSERT INTO dbo.SubscriptionTransactions (ItemValue, OrderNr, TransactionCredit, TransactionDate, TransactionDebit, TransactionDescription, TransactionType)
SELECT
T.Total
, O.OrderNr
, 0
, GetDate()
, F.Fee
, I.ItemCode
, CASE WHEN I.JobId IS NULL THEN 'Job' ELSE 'Item' END
FROM Inserted I
-- Deleted won't exist for an insert
LEFT JOIN Deleted D on D.OrderDetailId = I.OrderDetailId
INNER JOIN Orders O on O.OrderId = I.OrderId
CROSS APPLY (
VALUES (I.NrUnits * I.UnitNet, D.NrUnits * D.UnitNet)
) N (Net, OldNet)
CROSS APPLY (
VALUES (N.Net * (1.0 + N.Net * O.TaxRate / 100))
) T (Total)
CROSS APPLY (
VALUES (FLOOR(T.Total * #SyncRate * 100)/100)
) F (Fee)
WHERE (N.Net != N.OldNet OR D.OrderDetailId IS NULL) -- First continue condition
AND Fee >= 0.01; -- Second continue condition

Count the amount of Cases and set Status on CaseStatus table if every counted amount is the statusCode 601 in the Cursor

Hello I made a cursor that check every row and check the status of the Caseinventory and then sets the status on the caseStatus. Problem is if I set the last counted row to 601 and it changes the code to 3 (shown in the code) it will not check if the rest is 601 and then just set the status to 3 even though rest is 600 or 599.
How can I solve this? should I count the amount on the CaseInventory and then check the status on every counted row and then update if the value is 601? and how do you do this? first time using cursor.
ALTER PROCEDURE [dbo].[SetsStatusOnCaseStatusByCaseInventoryStatus]
#StatusID INT,
#ID int,
#CaseInventoryID int
AS
BEGIN
--find caseID on CaseInventoryID
declare #CaseID int = (SELECT CaseID from [dbo].[factCaseInventory] where ID=#ID)
--CaseStatus
declare #CaseIkkeStartet int =1
declare #CaseIgangStatus int = 2
declare #CaseDoneStatus int = 3
--CaseInventoryStatus
DECLARE #NotStarted int = 599
DECLARE #Nocode int = 600
declare #YesCode int = 601
--Cursor
declare CaseInventoryCursor cursor for
Select ID, StatusID, CaseID from [dbo].[factCaseInventory]
open CaseInventoryCursor
fetch next from CaseInventoryCursor into #ID, #StatusID, #CaseID
while(##FETCH_STATUS = 0)
begin
SELECT #StatusID =StatusID from [dbo].[factCaseInventory] where CaseID=#CaseID
if(#StatusID = #Nocode)
begin
update [dbo].[factCase] set CaseStatusID=#CaseDoneStatus where CaseId=#CaseID
end
else if (#StatusID =#YesCode)
begin
update [dbo].[factCase] set CaseStatusID=#CaseIgangStatus where CaseId=#CaseID
end
else if (#StatusID =#NotStarted)
begin
update [dbo].[factCase] set CaseStatusID=#CaseIkkeStartet where CaseId=#CaseID
end
fetch next from CaseInventoryCursor into #ID, #StatusID, #CaseID
end
close CaseInventoryCursor
deallocate CaseInventoryCursor
END
It's unclear to me why you're passing parameters to your stored procedure when you then either don't use them, or overwrite them with new values before you use them.
However, if what you're trying to do is update each factCase record with a new CaseStatusID value based upon the related factCaseInventory record's StatusID value, you can just do a simple UPDATE with a join:
update fc set CaseStatusID =
case fci.StatusID
when 600 then 3
when 601 then 2
when 599 then 1
end
from factCaseInventory fci
left join factCase fc on fvi.CaseID = fc.CaseID

Trigger that prevents update of column based on result of the user defined function

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.

Command Execution Exception: The INSERT statement conflicted with the FOREIGN KEY constraint

I have a table named student I want to assign a fee or update (if present), I am looping over them which worked fine until I deleted a student. So now whenever I run the stored procedure it shows error.
Here is my code.
ALTER PROC [dbo].[sp_AutoAssignFeeUpdate]
(
#FeeID int,
#FeeAmount int,
#Fine int,
#DueDate date,
#AppliedON date,
#FeeMonth varchar(30)
)
AS
--- Variables Using in Loops
DECLARE #LoopCounter INT , #MaxStudentID INT, #StdID INT, #FID INT
-- Setting Counter From the count of students in student table if they are 'Active'
SELECT #LoopCounter = min(AdmissionNumber) , #MaxStudentID = max(AdmissionNumber)
FROM StudentTable
-- WHILE Loop Condition
WHILE(#LoopCounter IS NOT NULL AND #LoopCounter <= #MaxStudentID )
BEGIN
--- SELECT IDs all Active students and matching with counter
SELECT #StdID = AdmissionNumber
FROM StudentTable WHERE AdmissionNumber = #LoopCounter AND Active = 'True'
--- CHECK IF ROW EXITS
SELECT #StdID = AdmissionNumber
FROM FeeAssociationTable
IF EXISTS ( SELECT FeeMonth FROM FeeAssociationTable
WHERE #LoopCounter = AdmissionNumber AND FeeID = #FeeID AND FeeMonth = #FeeMonth)
BEGIN
UPDATE FeeAssociationTable
SET FeeAmount = #FeeAmount, Fine = #Fine , DueDate = #DueDate
WHERE #LoopCounter = AdmissionNumber AND FeeID = #FeeID
AND FeeMonth = #FeeMonth
END
ELSEBEGIN
INSERT FeeAssociationTable
(FeeID, AdmissionNumber, FeeAmount, FeeMonth, DueDate, Fine, AppliedOn, [Status])
VALUES
(#FeeID, #LoopCounter, #FeeAmount, #FeeMonth, #DueDate, #Fine, #AppliedON, 'Pending')
END
SET #LoopCounter = #LoopCounter + 1
END
This is working if the Ids are continuous. What should I do if there is an Id missing or how to skip that specific number which is not present in the studentTable.
Explanation:
The loop take the initial value of min(id) from studentTable set as counter, and final value of max(id).
The loop compares both values id in studentTable and counter of the loop.
Then for each counter student in the table the the fee is assigned.
INSERT FeeAssociationTable
(FeeID, AdmissionNumber, FeeAmount, FeeMonth, DueDate, Fine, AppliedOn, [Status])
VALUES
(#FeeID, #LoopCounter, #FeeAmount, #FeeMonth, #DueDate, #Fine,
The problem is here, while inserting I am using #LoopCounter. Lets say #LoopCounter = 100 but StudentTable is skipping 100 and there is 101 there. The conflict rises. Because the SQL can't find the **100** id in the studentTable.
Thanks in Advance.
As I said in a comment, this whole thing looks like it can be replaced by a MERGE. Don't do things one step at a time when you can tell the server what to do with the entire set of rows.
Something like:
MERGE INTO FeeAssociationTable t
USING (SELECT AdmissionNumber, #FeeID as FeeID, #FeeMonth as FeeMonth FROM StudentTable
WHERE Active = 'True') s
ON t.AdmissionNumber = s.AdmissionNumber AND
t.FeeID = s.FeeID AND
t.FeeMonth = s.FeeMonth
WHEN MATCHED THEN UPDATE SET FeeAmount = #FeeAmount, Fine = #Fine , DueDate = #DueDate
WHEN NOT MATCHED THEN INSERT
(FeeID, AdmissionNumber, FeeAmount, FeeMonth, DueDate, Fine, AppliedOn, [Status])
VALUES
(#FeeID, s.AdmissionNumber, #FeeAmount, #FeeMonth, #DueDate, #Fine, #AppliedON, 'Pending');
Not sure I've got all of the conditions quite right, but you should be able to see what I'm driving at, I hope.
Your actual issue could have been "solved" by replacing:
SET #LoopCounter = #LoopCounter + 1
with:
SELECT #LoopCounter = MIN(AdmissionNumber) FROM StudentTable
WHERE Active = 'True' and AdmissionNumber > #LoopCounter
but don't do that, please.
Man , you should use a For each . For example:
DECLARE yourCursor CURSOR LOCAL STATIC
FOR SELECT AdmissionNumber
FROM StudentTable
OPEN yourCursor
FETCH NEXT FROM yourCursor INTO #StdID
WHILE ##FETCH_STATUS = 0
BEGIN
/*
CHECK IF EXIST FOR UPDATE OR INSERT
*/
FETCH NEXT FROM yourCursor INTO #StdID
END
CLOSE yourCursor
DEALLOCATE yourCursor
GO
This replace your while.

Update only one row using a stored procedure with a cursor, each time the stored procedure is called

I am very new to using cursors.
I'm trying to make only one row change the freight value (not all), each time the stored procedure is ran. Any help would be greatly appreciated.
[Code]
SELECT * FROM NorthWind.dbo.Orders ORDER BY ShipVia, Freight --Checking...
IF (OBJECT_ID('spUpateOrder')IS NOT NULL) DROP PROCEDURE spUpateOrder;
GO
CREATE PROCEDURE spUpateOrder
#CustomerID nchar(5)
,#EmployeeID [int]
,#OrderDate datetime = NULL
,#RequiredDate datetime = NULL
,#ShippedDate datetime = NULL
,#ShipVia int = 1 -- may have a primary shipper
,#Freight money
,#ShipName nvarchar(40) = NULL
,#ShipAddress nvarchar(60) = NULL
,#ShipCity nvarchar(15) = NULL
,#ShipRegion nvarchar(15) = NULL
,#ShipPostalCode nvarchar(10) = NULL
,#ShipCountry nvarchar(15) = NULL
AS
DECLARE UpdateOneRowCur CURSOR LOCAL FOR
SELECT Freight FROM [NorthWind].[dbo].[Orders] -- WHERE Freight = NULL
WHERE CustomerID = 'alfki' AND Freight < 30.00 AND Freight IS NOT NULL
SET #Freight = #Freight + .1
OPEN UpdateOneRowCur
FETCH NEXT FROM UpdateOneRowCur INTO #Freight
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC spUpateOrder
#CustomerID
,#EmployeeID
,#OrderDate
,#RequiredDate
,#ShippedDate
,#ShipVia --= 1, -- may have a primary shipper
,#Freight
,#ShipName
,#ShipAddress
,#ShipCity
,#ShipRegion
,#ShipPostalCode
,#ShipCountry
FETCH NEXT FROM UpdateOneRowCur INTO #Freight
RETURN ##Identity
DECLARE #Ret int
EXECUTE #Ret = #Freight;
--EXECUTE #Ret = spUpateOrder --'alfki', 7, '1/1/2013', null, null, 1
, null;
IF #ret = 0
PRINT 'error!';
ELSE
PRINT 'OrderId entered: ' + CAST(#ret as varchar);
CLOSE UpdateOneRowCur
DEALLOCATE UpdateOneRowCur
END;
GO
Declare #Ret int
EXEC #Ret = spUpateOrder 'alfki', 7, '1/1/2013', null, null, 1, 25.99;
GO
[/Code]
This code does change all the rows at once, but I only want it to change the first row it finds and then the second row it finds next time I run the stored procedure. Not sure how to do this? Note: I made some new rows with the same freight value 25.99 in order to test it.
Answer:
Here is what I did to change only one row each time the stored procedure was executed: (This does work well!)
[code]
--============== UPdate only one row ===============--
CREATE PROCEDURE uspUpdateOrder
#Freight money --Testing.
AS
/*
Created by: Chris Singleton
02/26/2017
Updates each row that has 25.99 where the customer's name is 'alfki'.
*/
BEGIN
--DECLARE #Freight
UPDATE top (1) Northwind.dbo.Orders
SET Freight = #Freight + .1
WHERE CustomerID = 'alfki' AND Freight = 25.99
END;
--============ Call the Stored Procedure =============--
GO
-- the call:
Declare #Ret int
EXEC #Ret = uspUpdateOrder 25.99;
If #ret = 0
print 'error!';
else
print 'OrderId entered: ' + cast(#ret as varchar);
DROP PROCEDURE uspUpdateOrder
GO
SELECT * FROM [Northwind].[dbo].[Orders] WHERE CustomerID = 'alfki'
--====================================--
[/code]
You have multiple issues in your code:
You are passing the'#Freight'as a parameter. In your example, you are assigning a value of 25.99 to this variable
Then you increase the value of the variable by 0.1 (SET #Freight = #Freight + .1). So, in your example, the value of the variable will be 26.09
Then you run a query and overwrite the value of #Freight by the result of your query (FETCH NEXT FROM UpdateOneRowCur INTO #Freight). At this point, the step 1 and 2 are completely redundant, because you are overwriting what you had before.
You are using a loop and in your loop, you keep reading one row from the result set of your query and overwrite the value of #Freigh and call the update procedure.
If you just need to update first row, then you could write this:
SELECT top 1 #Freight = Freight FROM [NorthWind].[dbo].[Orders] -- WHERE Freight = NULL
WHERE CustomerID = 'alfki' AND Freight < 30.00 AND Freight IS NOT NULL
SET #Freight = #Freight + .1 -- I'm not sure if you realy want to do this or not
The change that I made in your query is that I am returning only one row (select top 1) and at the same time assign the value of #Freight
You don't really need any cursor or loop.
Sparrow is right in a way, that I don't need a cursor to do the coding, so I gave Sparrow points, but in order to focus on one row with filters, you can do this.
[Code]
--============== UPdate only one row ===============--
CREATE PROCEDURE uspUpdateOrder
#Freight money --Testing.
AS
/*
Created by: Chris Singleton
02/26/2017
Updates each row that has 25.99 where the customer's name is 'alfki'.
*/
BEGIN
--DECLARE #Freight
UPDATE top (1) Northwind.dbo.Orders
SET Freight = #Freight + .1
WHERE CustomerID = 'alfki' AND Freight = 25.99
END;
--============ Call the Stored Procedure =============--
GO
-- the call:
Declare #Ret int
EXEC #Ret = uspUpdateOrder 25.99;
If #ret = 0
print 'error!';
else
print 'OrderId entered: ' + cast(#ret as varchar);
DROP PROCEDURE uspUpdateOrder
GO
SELECT * FROM [Northwind].[dbo].[Orders] WHERE CustomerID = 'alfki'
--====================================--
[/Code]
Note: That you can focus on the row using a combination of the update clause TOP (1) and the where clause as a filter. This work's nicely!