您的帖子已有几个月的历史,但是这对其他人也可能有用。
Teradata 的 Developer Exchange 上有一个类似的问题,当时我记得几年前我将它移植到了 Teradata。快速搜索其他解决方案将我引导到这篇文章。
由于 Teradata 支持 ROWS UNBOUNDED PRECEDING(微软在 SS2012 中添加了这一点),结果变得更加简单:
关于你的问题:
a: CTE 可以用 Derived Tables 代替,它只是一种语法变化。
b: CROSS/OUTER APPLY 是 SQL Server 专有语法,有时可以用 [OUTER] JOIN 代替,在这种情况下,它只是一种复杂的累积求和方式。
c:当优化器没有做好计划时,索引提示应该是最后的手段
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
它基于与此处描述的获胜解决方案相同的逻辑:
https ://www.simple-talk.com/sql/performance/set-based-speed-phreakery-the-fifo-stock-inventory-sql-problem/
这将运行得非常快,您不必创建 SQL Server 所需的任何索引 :-)
将其移植到其他 DBMS 的备注:
它是普通的标准 SQL,只有 QUALIFY 是 Teradata 特定的。QUALIFY 与 GROUP BY 的 HAVING 相同,过滤 OLAP 函数的结果。可以通过将条件移动到外层的 WHERE 轻松替换它。