SQL 服务器 - 具有 2 个表的 CTE,直到消耗数量
SQL Server - CTE with 2 tables until qty consumed
我正在尝试对我的 2 个表(SQL Server 2012)执行递归连接,如下所示:
Table: 购买
szProductID nQty szSupplierCode
0001 5 A-101
0001 50 A-102
0001 2 A-103
0001 70 A-104
和
Table:销售额
szProductID nQty szSalesID
0001 10 S-101
0001 20 S-102
0001 20 S-103
0001 50 S-104
我需要这样的结果:
szProductID nQtySales SupplierCode SalesID
0001 5 A-101 S-101
0001 5 A-102 S-101
0001 20 A-102 S-102
0001 20 A-102 S-103
0001 5 A-102 S-104
0001 2 A-103 S-104
0001 43 A-104 S-104
目标是查找 szSupplierCode 售出的商品数量。我找到了很多进行选择的示例,但我不确定 CTE 是否可以解决我的问题。
如果有人可以用 CTE 或游标确认这是可能的,我将不胜感激。
谢谢!
您可以使用 Recursive CTE
:
;WITH PurchaseRN AS (
-- Add row number field to Purchase table
SELECT szProductID, nQty, szSupplierCode,
ROW_NUMBER() OVER (PARTITION BY szProductID
ORDER BY szSupplierCode) AS rn
FROM Purchase
), SalesRN AS (
-- Add row number field to Sales table
SELECT szProductID, nQty, szSalesID,
ROW_NUMBER() OVER (PARTITION BY szProductID
ORDER BY szSalesID) AS rn
FROM Sales
), ConsumePurchases AS (
-- Consume 1st Sales record using 1st Purchase record
SELECT p.szProductID,
IIF(p.nQty > s.nQty, s.nQty, p.nQty) AS nQtySales,
p.szSupplierCode AS SupplierCode,
s.szSalesID AS SalesID,
-- Propagate un-consumed Purchase/Sales quantities to next recursion level
IIF(p.nQty > s.nQty, p.nQty - s.nQty, 0) AS pResidue,
IIF(p.nQty > s.nQty, 0, s.nQty- p.nQty) AS sResidue,
-- Purchase row number processed by current recursion level
1 AS prn,
-- Sales row number processed by current recursion level
1 AS srn
FROM PurchaseRN AS p
INNER JOIN SalesRN AS s ON p.szProductID = s.szProductID
WHERE p.rn = 1 AND s.rn = 1
UNION ALL
SELECT p.szProductID,
-- Calculate Sales quantity consumed by current recursion level
-- If un-consumed Purchase/Sales quantities exist from previous level
-- then use this instead of nQty field.
IIF(c.pResidue > 0,
IIF(c.pResidue > s.nQty, s.nQty, c.pResidue),
IIF(c.sResidue > 0,
IIF(p.nQty > c.sResidue, c.sResidue, p.nQty),
IIF(p.nQty > s.nQty, s.nQty, p.nQty))) AS nQtySales,
p.szSupplierCode AS SupplierCode,
s.szSalesID AS SalesID,
x.pResidue,
x.sResidue,
x.prn AS prn,
x.srn AS srn
FROM PurchaseRN AS p
INNER JOIN SalesRN AS s ON p.szProductID = s.szProductID
INNER JOIN ConsumePurchases AS c ON c.szProductID = s.szProductID
CROSS APPLY (
SELECT -- if previous Purchare record is not fully consumed (c.pResidue > 0)
-- then stay at the same Purchase record (c.prn), else get next record.
CASE
WHEN c.pResidue > 0 THEN c.prn
ELSE c.prn + 1
END AS prn,
-- if previous Sales record is not fully consumed (c.sResidue > 0)
-- then stay at the same Sales record (c.srn), else get next record.
CASE
WHEN c.sResidue > 0 THEN c.srn
ELSE c.srn + 1
END AS srn,
-- calculate Sales quantity left un-cosumed (sResidue) after current record
-- has been processed
CASE
WHEN c.sResidue > 0 THEN IIF(c.sResidue - p.nQty > 0, c.sResidue - p.nQty, 0)
WHEN c.pResidue > 0 THEN IIF(c.pResidue > s.nQty, 0, s.nQty - c.pResidue)
ELSE IIF(p.nQty > s.nQty, p.nQty - s.nQty, 0)
END AS sResidue,
-- calculate Purchase quantity left un-cosumed (pResidue) after current record
-- has been processed
CASE
WHEN c.pResidue > 0 THEN IIF(c.pResidue - s.nQty > 0, c.pResidue - s.nQty, 0)
WHEN c.sResidue > 0 THEN IIF(p.nQty > c.sResidue, p.nQty - c.sResidue, 0)
ELSE IIF(p.nQty > s.nQty, p.nQty - s.nQty, 0)
END AS pResidue) AS x(prn, srn, sResidue, pResidue)
-- Continue until there are no more Purchase/Sales records to process
WHERE p.rn = x.prn AND s.rn = x.srn
)
SELECT szProductID, nQtySales, SupplierCode, SalesID
FROM ConsumePurchases
谢谢乔治...
我为此工作了 3 天多,而你一天就完成了...
但我用不同的方法做了 200 多行...:)
首先我通过计算所有购买和销售(总计)来做到这一点,如下所示:
Table: PurchaseTmp
szProductID szSupplierCode nQtyPurchase nQtySold
0001 A-101 5 5
0001 A-102 50 50
0001 A-103 2 2
0001 A-104 70 43
之后我逐行迭代...并使用 Fetch Next Statement 生成了 200 多行代码..
再次..谢谢你 Giorgos...
已回答此问题。
很有可能 cursors/recursion 可以使用 Windowed Aggregate Functions 重写。在您的情况下,可以通过计算 purchased/sold 数量的累积总和然后加入重叠范围来完成:
with a as
(
SELECT id, szProductID, szSupplierCode, nQty,
--cumulative sum of quantities
SUM(nQty) OVER (PARTITION BY szProductID ORDER BY id ROWS UNBOUNDED PRECEDING) AS cumsum
FROM Purchase
)
, b as
(
SELECT id, szProductID, szSalesID, nQty,
--cumulative sum of quantities
SUM(nQty) OVER (PARTITION BY szProductID ORDER BY id ROWS UNBOUNDED PRECEDING) AS cumsum
FROM Sales
)
SELECT
a.szSupplierCode,b.szSalesID,
-- calculate the assigned quantity
CASE WHEN a.cumsum < b.cumsum THEN a.cumsum ELSE b.cumsum END
-CASE WHEN a.cumsum -a.nQty > b.cumsum - b.nQty THEN a.cumsum - a.nQty ELSE b.cumsum - b.nQty END
FROM a
JOIN b
ON a.szProductID = b.szProductID
AND a.cumsum > b.cumsum - b.nQty -- check for overlapping cumultive sums
AND a.cumsum - a.nQty < b.cumsum
id
是确定排序顺序的任何列,例如日期或 szSalesID
/szSupplierCode
.
SQL服务器不支持LEAST
/GREATEST
,否则数量计算会更简单:
LEAST(a.cumsum, b.cumsum) - GREATEST(a.cumsum -a.nQty, b.cumsum - b.nQty)
我劫持了@GiorgosBetsos fiddle :)
我正在尝试对我的 2 个表(SQL Server 2012)执行递归连接,如下所示:
Table: 购买
szProductID nQty szSupplierCode
0001 5 A-101
0001 50 A-102
0001 2 A-103
0001 70 A-104
和
Table:销售额
szProductID nQty szSalesID
0001 10 S-101
0001 20 S-102
0001 20 S-103
0001 50 S-104
我需要这样的结果:
szProductID nQtySales SupplierCode SalesID
0001 5 A-101 S-101
0001 5 A-102 S-101
0001 20 A-102 S-102
0001 20 A-102 S-103
0001 5 A-102 S-104
0001 2 A-103 S-104
0001 43 A-104 S-104
目标是查找 szSupplierCode 售出的商品数量。我找到了很多进行选择的示例,但我不确定 CTE 是否可以解决我的问题。
如果有人可以用 CTE 或游标确认这是可能的,我将不胜感激。
谢谢!
您可以使用 Recursive CTE
:
;WITH PurchaseRN AS (
-- Add row number field to Purchase table
SELECT szProductID, nQty, szSupplierCode,
ROW_NUMBER() OVER (PARTITION BY szProductID
ORDER BY szSupplierCode) AS rn
FROM Purchase
), SalesRN AS (
-- Add row number field to Sales table
SELECT szProductID, nQty, szSalesID,
ROW_NUMBER() OVER (PARTITION BY szProductID
ORDER BY szSalesID) AS rn
FROM Sales
), ConsumePurchases AS (
-- Consume 1st Sales record using 1st Purchase record
SELECT p.szProductID,
IIF(p.nQty > s.nQty, s.nQty, p.nQty) AS nQtySales,
p.szSupplierCode AS SupplierCode,
s.szSalesID AS SalesID,
-- Propagate un-consumed Purchase/Sales quantities to next recursion level
IIF(p.nQty > s.nQty, p.nQty - s.nQty, 0) AS pResidue,
IIF(p.nQty > s.nQty, 0, s.nQty- p.nQty) AS sResidue,
-- Purchase row number processed by current recursion level
1 AS prn,
-- Sales row number processed by current recursion level
1 AS srn
FROM PurchaseRN AS p
INNER JOIN SalesRN AS s ON p.szProductID = s.szProductID
WHERE p.rn = 1 AND s.rn = 1
UNION ALL
SELECT p.szProductID,
-- Calculate Sales quantity consumed by current recursion level
-- If un-consumed Purchase/Sales quantities exist from previous level
-- then use this instead of nQty field.
IIF(c.pResidue > 0,
IIF(c.pResidue > s.nQty, s.nQty, c.pResidue),
IIF(c.sResidue > 0,
IIF(p.nQty > c.sResidue, c.sResidue, p.nQty),
IIF(p.nQty > s.nQty, s.nQty, p.nQty))) AS nQtySales,
p.szSupplierCode AS SupplierCode,
s.szSalesID AS SalesID,
x.pResidue,
x.sResidue,
x.prn AS prn,
x.srn AS srn
FROM PurchaseRN AS p
INNER JOIN SalesRN AS s ON p.szProductID = s.szProductID
INNER JOIN ConsumePurchases AS c ON c.szProductID = s.szProductID
CROSS APPLY (
SELECT -- if previous Purchare record is not fully consumed (c.pResidue > 0)
-- then stay at the same Purchase record (c.prn), else get next record.
CASE
WHEN c.pResidue > 0 THEN c.prn
ELSE c.prn + 1
END AS prn,
-- if previous Sales record is not fully consumed (c.sResidue > 0)
-- then stay at the same Sales record (c.srn), else get next record.
CASE
WHEN c.sResidue > 0 THEN c.srn
ELSE c.srn + 1
END AS srn,
-- calculate Sales quantity left un-cosumed (sResidue) after current record
-- has been processed
CASE
WHEN c.sResidue > 0 THEN IIF(c.sResidue - p.nQty > 0, c.sResidue - p.nQty, 0)
WHEN c.pResidue > 0 THEN IIF(c.pResidue > s.nQty, 0, s.nQty - c.pResidue)
ELSE IIF(p.nQty > s.nQty, p.nQty - s.nQty, 0)
END AS sResidue,
-- calculate Purchase quantity left un-cosumed (pResidue) after current record
-- has been processed
CASE
WHEN c.pResidue > 0 THEN IIF(c.pResidue - s.nQty > 0, c.pResidue - s.nQty, 0)
WHEN c.sResidue > 0 THEN IIF(p.nQty > c.sResidue, p.nQty - c.sResidue, 0)
ELSE IIF(p.nQty > s.nQty, p.nQty - s.nQty, 0)
END AS pResidue) AS x(prn, srn, sResidue, pResidue)
-- Continue until there are no more Purchase/Sales records to process
WHERE p.rn = x.prn AND s.rn = x.srn
)
SELECT szProductID, nQtySales, SupplierCode, SalesID
FROM ConsumePurchases
谢谢乔治...
我为此工作了 3 天多,而你一天就完成了...
但我用不同的方法做了 200 多行...:)
首先我通过计算所有购买和销售(总计)来做到这一点,如下所示:
Table: PurchaseTmp
szProductID szSupplierCode nQtyPurchase nQtySold
0001 A-101 5 5
0001 A-102 50 50
0001 A-103 2 2
0001 A-104 70 43
之后我逐行迭代...并使用 Fetch Next Statement 生成了 200 多行代码..
再次..谢谢你 Giorgos...
已回答此问题。
很有可能 cursors/recursion 可以使用 Windowed Aggregate Functions 重写。在您的情况下,可以通过计算 purchased/sold 数量的累积总和然后加入重叠范围来完成:
with a as
(
SELECT id, szProductID, szSupplierCode, nQty,
--cumulative sum of quantities
SUM(nQty) OVER (PARTITION BY szProductID ORDER BY id ROWS UNBOUNDED PRECEDING) AS cumsum
FROM Purchase
)
, b as
(
SELECT id, szProductID, szSalesID, nQty,
--cumulative sum of quantities
SUM(nQty) OVER (PARTITION BY szProductID ORDER BY id ROWS UNBOUNDED PRECEDING) AS cumsum
FROM Sales
)
SELECT
a.szSupplierCode,b.szSalesID,
-- calculate the assigned quantity
CASE WHEN a.cumsum < b.cumsum THEN a.cumsum ELSE b.cumsum END
-CASE WHEN a.cumsum -a.nQty > b.cumsum - b.nQty THEN a.cumsum - a.nQty ELSE b.cumsum - b.nQty END
FROM a
JOIN b
ON a.szProductID = b.szProductID
AND a.cumsum > b.cumsum - b.nQty -- check for overlapping cumultive sums
AND a.cumsum - a.nQty < b.cumsum
id
是确定排序顺序的任何列,例如日期或 szSalesID
/szSupplierCode
.
SQL服务器不支持LEAST
/GREATEST
,否则数量计算会更简单:
LEAST(a.cumsum, b.cumsum) - GREATEST(a.cumsum -a.nQty, b.cumsum - b.nQty)
我劫持了@GiorgosBetsos fiddle :)