SQL Server - CTE with 2 tables until qty consumed

夙愿已清 提交于 2019-12-12 17:30:43

问题


I am try to perform a recursive join of my 2 tables (SQL Server 2012) like below:

Table: Purchase

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

and

Table: Sales

szProductID     nQty     szSalesID
0001            10       S-101
0001            20       S-102
0001            20       S-103
0001            50       S-104

And I need my result like this :

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

The goal is to find how many item sold by szSupplierCode. I've found plenty of examples for doing selects but i'm not sure if CTE can solve my problem.

If anyone can confirm this is possible with a CTE or cursor, I'd appreciate it.

Thanks!


回答1:


You can use a 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




回答2:


Thank you Giorgos...

I've been working in more than 3days for this and you did it in a day...

But i did it with different approach and with more than 200 lines...:)

first i did it by calculate all the purchase and sales (total) like below:

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

After that i iterate row by row...and it makes more than 200 lines of code by using Fetch Next Statement..

Again..thank you Giorgos...

This question is answered.




回答3:


There's a high probability that cursors/recursion can be rewritten using Windowed Aggregate Functions. In your case it can be done by calculating cumulative sums of the purchased/sold quantities and then join on overlapping ranges:

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 is any column determining the sort order, e.g. a date or szSalesID/szSupplierCode.

SQL Server doesn't support LEAST/GREATEST, otherwise the quantity calculation would be easier:

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

I highjacked @GiorgosBetsos fiddle :)



来源:https://stackoverflow.com/questions/33094828/sql-server-cte-with-2-tables-until-qty-consumed

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!