'Running total in SQL with a reset condition
I have a data that combines inventory, sales forecast, and future supply data for different parts. I have combined it to give me a table that gives a net quantity each month for each part. See below:
Date | Part | Net Quantity |
---|---|---|
30/06/2021 | A | 1000 |
31/07/2021 | A | -150 |
31/08/2021 | A | -200 |
30/09/2021 | A | -500 |
31/10/2021 | A | -200 |
30/11/2021 | A | -200 |
31/12/2021 | A | 50 |
30/06/2021 | B | 100 |
31/07/2021 | B | -80 |
31/08/2021 | B | 20 |
30/09/2021 | B | -30 |
31/10/2021 | B | -35 |
30/11/2021 | B | -40 |
31/12/2021 | B | -150 |
I need to create a running total view that partitions by a part number and resets in the next month if it goes below 0. The starting point for the running total needs to be 0 if the previous month's closing inventory was negative.
My expected outcome is this:
Date | Part | Net Quantity | Closing Inventory (Expected Outcome) |
---|---|---|---|
30/06/2021 | A | 1000 | 1000 |
31/07/2021 | A | -150 | 850 |
31/08/2021 | A | -200 | 650 |
30/09/2021 | A | -500 | 150 |
31/10/2021 | A | -200 | -50 |
30/11/2021 | A | -200 | -200 |
31/12/2021 | A | 50 | 50 |
30/06/2021 | B | 100 | 100 |
31/07/2021 | B | -80 | 20 |
31/08/2021 | B | 20 | 40 |
30/09/2021 | B | -30 | 10 |
31/10/2021 | B | -35 | -25 |
30/11/2021 | B | -40 | -40 |
31/12/2021 | B | -150 | -150 |
My current code is:
SELECT
Date,
Part,
Net_Quantity,
sum(Net_Quantity) over (partition by Part order by date) 'Closing_Inventory'
FROM grouped
I am unsure how to make a condition based on the last value in the running total, the lag
function is not able to view the previous row of the running total.
Solution 1:[1]
It would have been easier if you had provided some sample data, but here is how I would solve this problem:
Declare @testData Table ([Date] date, Part char(1), NetQuantity int);
Insert Into @testData ([Date], Part, NetQuantity)
Values ('2021-06-30', 'A', 1000)
, ('2021-07-31', 'A', -150)
, ('2021-08-31', 'A', -200)
, ('2021-09-30', 'A', -500)
, ('2021-10-31', 'A', -200)
, ('2021-11-30', 'A', -200)
, ('2021-12-31', 'A', 50)
, ('2021-06-30', 'B', 100)
, ('2021-07-31', 'B', -80)
, ('2021-08-31', 'B', 20)
, ('2021-09-30', 'B', -30)
, ('2021-10-31', 'B', -35)
, ('2021-11-30', 'B', -40)
, ('2021-12-31', 'B', -150);
With runningTotal
As (
Select *
, Inventory = sum(td.NetQuantity) over(Partition By td.Part Order By td.[Date])
From @testData td
)
Select [Date]
, Part
, NetQuantity
, ClosingInventory = iif(lag(Inventory, 1, 0) over(Partition By Part Order By [Date]) < 0, NetQuantity, Inventory)
From runningTotal;
Solution 2:[2]
What about something like this...
--Add a column...
ALTER TABLE PartsInventory ADD RowNumWithinPart int;
--Assign row numbers partitioned by Part...
UPDATE PartsInventory
SET RowNumWithinPart = x.rownum
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY Part ORDER BY [Date]) as rownum, *
FROM PartsInventory
) x
WHERE x.Part = PartsInventory.Part AND x.Date = PartsInventory.Date
--Recursive CTE solution:
WITH InvTotalsCTE AS
(
SELECT [Date], RowNumWithinPart, Part, Net_Quantity, Net_Quantity as Closing_Inventory
FROM PartsInventory
WHERE RowNumWithinPart = 1
UNION ALL
SELECT Q.[Date], Q.RowNumWithinPart, Q.Part, Q.Net_Quantity, iif(P.Closing_Inventory < 0, 0, P.Closing_Inventory) + Q.Net_Quantity as Closing_Inventory
FROM InvTotalsCTE as P
JOIN PartsInventory Q ON (Q.RowNumWithinPart - 1) = P.RowNumWithinPart AND Q.Part = P.Part
)
SELECT [Date], Part, RowNumWithinPart, Net_Quantity, Closing_Inventory
FROM InvTotalsCTE
ORDER BY Part, RowNumWithinPart
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Jeff |
Solution 2 | General Grievance |