问题
There is an old stack post (First-in-first-out (FIFO) inventory costing) that contains the Set-based Speed Phreakery: The FIFO Stock Inventory SQL Problem: (https://www.simple-talk.com/sql/performance/set-based-speed-phreakery-the-fifo-stock-inventory-sql-problem/).
I have been trying to adapt it from SQL Server to Teradata SQL but have discovered that:
(a) Teradata can only handle one CTE with statement
(b) You cannot use cross apply
(c) You cannot use hint indexes?
My questions are:
Is there an alternative in Teradata (other than volatile tables) to get around (a) above?
Is the Terdata "Cross Join" the same as Cross Apply in SQL Server?
Has anyone adapated this script to Teradata?
回答1:
Your post is a few months old, but nevertheless this might be useful for others, too.
There was a similar question on Teradata's Developer Exchange, when i remembered that i ported it to Teradata a few years ago. A quick search for other solutions routed me to this post.
It turned out to be much simpler due to Teradata's support of ROWS UNBOUNDED PRECEDING (Microsoft added that in SS2012):
Regarding your questions:
a: CTE can be replaced by Derived Tables, it's just a syntax variation.
b: CROSS/OUTER APPLY is SQL Server proprietary syntax, which can sometimes be replaced by an [OUTER] JOIN, in this case it was just a complicated way to do a cumulative sum.
c: Index hints should be a last resort when the optimizer is not doing a good plan
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
It's based on the same logic as the winning solution described here: https://www.simple-talk.com/sql/performance/set-based-speed-phreakery-the-fifo-stock-inventory-sql-problem/
This will be running quite fast and you don't have to create any of the indexes needed for SQL Server :-)
Remark for porting it to other DBMSes:
It's plain Standard SQL, only the QUALIFY is Teradata specific. QUALIFY is the same as a HAVING for GROUP BY, filtering the result of an OLAP function. It can be easily replaced by moving the condition into a WHERE in the outer level.
回答2:
you can use the Derived tables instead of CTE
来源:https://stackoverflow.com/questions/17137015/teradata-fifo-script