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

Demo here

谢谢乔治...

我为此工作了 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 :)