Hi newb1,
you got luck, when i saw that competition a few years ago i tried to solve it using Teradata SQL :-)
It turned out to be much simpler due to Teradata's support of ROWS UNBOUNDED PRECEDING (Microsoft added that in SS2012):
SELECT
ArticleId
,SUM(ItemCnt) AS CurrentItems -- same as TotalStock
,SUM(ItemCnt * CurrentPrice) AS CurrentValue
FROM
(
SELECT
ArticleId
-- how many items will be used from this transaction, maybe less than all for the oldest row
,CASE WHEN RollingStock + Items > TotalStock THEN TotalStock - RollingStock ELSE Items END AS ItemCnt
-- find the latest IN-price for RET rows
,MAX(Price)
OVER (PARTITION BY ArticleID, PriceGroup
ORDER BY TranDate) AS CurrentPrice
FROM
(
SELECT
ArticleId ,TranDate ,Price ,Items --,TranCode
-- dummy column to get the current price in the next step, new group starts with every 'IN'
,SUM(CASE WHEN TranCode = 'IN' THEN 1 ELSE 0 END)
OVER (PARTITION BY ArticleID
ORDER BY TranDate
ROWS UNBOUNDED PRECEDING) AS PriceGroup
-- Aggregating all in/out movements -> number of items left in stock after all transactions
,SUM(CASE WHEN TranCode IN ('IN', 'RET') THEN Items ELSE -Items END)
OVER (PARTITION BY ArticleID) AS TotalStock
-- reverse sum of all inbound IN/RET movements
,SUM(CASE WHEN TranCode IN ('IN', 'RET') THEN Items END)
OVER (PARTITION BY ArticleID)
-SUM(CASE WHEN TranCode IN ('IN', 'RET') THEN Items END)
OVER (PARTITION BY ArticleID
ORDER BY TranDate
ROWS UNBOUNDED PRECEDING) AS RollingStock
/*
-- same as above, simpler syntax, but different ORDER BY results in extra STATS step in explain
,COALESCE(SUM(CASE WHEN TranCode IN ('IN', 'RET') THEN Items END)
OVER (PARTITION BY ArticleID
ORDER BY TranDate DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING),0) AS RollingStock
*/
/* -- cumulative sum, not needed to get the result
,SUM(CASE WHEN TranCode IN ('IN', 'RET') THEN Items ELSE -Items END)
OVER (PARTITION BY ArticleID
ORDER BY TranDate
ROWS UNBOUNDED PRECEDING) AS CurrentItems
*/
FROM Stock
-- only keep the row needed to calculate the value
-- plus all IN rows to find the current price for RET rows in the next step
-- to exclude items out of stock: add "AND (TotalStock > 0)"
QUALIFY ((TranCode = 'IN') OR (RollingStock <= TotalStock AND TranCode = 'RET'))AND (TotalStock > 0)
) AS dt
-- remove older IN rows
QUALIFY ItemCnt >= 0
) AS dt
GROUP BY 1
ORDER BY 1
The nested CurrentPrice calculation could be replaced using RESET WHEN in TD13.10 but nesting allows to reduce the number of rows before the 2nd STATS step:
,MAX(Price)
OVER (PARTITION BY ArticleID
ORDER BY TranDate
RESET WHEN price > 0
ROWS UNBOUNDED PRECEDING) AS CurrentPrice
It's based on the same logic as the winning solution:
https://www.simple-talk.com/sql/performance/set-based-speed-phreakery-the-fifo-stock-inventory-sql-problem/
Dieter
Hi newb1,
you got luck, when i saw that competition a few years ago i tried to solve it using Teradata SQL :-)
It turned out to be much simpler due to Teradata's support of ROWS UNBOUNDED PRECEDING (Microsoft added that in SS2012):
The nested CurrentPrice calculation could be replaced using RESET WHEN in TD13.10 but nesting allows to reduce the number of rows before the 2nd STATS step:
It's based on the same logic as the winning solution:
https://www.simple-talk.com/sql/performance/set-based-speed-phreakery-the-fifo-stock-inventory-sql-problem/
Dieter