SQL Server - 带有2个表的CTE,直到消耗数量为止

时间:2015-10-13 05:32:54

标签: sql-server sql-server-2012 common-table-expression

我尝试执行我的2个表(SQL Server 2012)的递归连接,如下所示:

表:购买

szProductID    nQty     szSupplierCode
0001           5        A-101
0001           50       A-102
0001           2        A-103
0001           70       A-104

表:销售

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或光标确认这是可行的,我会很感激。

谢谢!

3 个答案:

答案 0 :(得分:1)

您可以使用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

答案 1 :(得分:0)

谢谢Giorgos ......

我为此工作超过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 ......

这个问题已经回答了。

答案 2 :(得分:0)

使用窗口化聚合函数可以重写游标/递归的概率很高。在您的情况下,可以通过计算购买/销售数量的累计总和,然后加入重叠范围来完成:

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 Server不支持LEAST / GREATEST,否则数量计算会更容易:

LEAST(a.cumsum, b.cumsum) - GREATEST(a.cumsum -a.nQty, b.cumsum - b.nQty)

我劫持了@GiorgosBetsos fiddle:)