Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

SQL Server - CTE avec 2 tables jusqu'à la quantité consommée

Vous pouvez utiliser un 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

Démo ici