'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