CTE - 减少库存量

时间:2013-05-24 11:13:21

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

我希望用MS SQL中的CTE解决下面的问题,但我正在碰壁砖。

这是我的问题。

订单表:

OrderID Item    Quantity    
------------------------
1       pen     80      
2       pen     30      
3       pen     25

库存表:

Inv ID  Lot  Item  Quantity
---------------------------
1       001  pen   100
2       002  pen   20
3       003  pen   30

我需要做的是处理订单,以便第一个订单来自第1批,第二个订单来自第1批和第2批,第三个订单来自第2批和第3批。

我需要知道哪个订单来自哪个订单,这意味着我不能只是将订单分组。

所以基本上我需要类似的东西:

OrderID Item  QuantityOrdered Lot  QuantityFromLot
--------------------------------------------------
1       pen   80              001  80
2       pen   30              001  20
2       pen   30              002  10
3       pen   25              002  10
3       pen   25              003  15  

有没有办法用CTE做到这一点?如果没有,你会推荐什么?

1 个答案:

答案 0 :(得分:1)

对于这类问题,C#或其他一些应用层解决方案可能是最好的方法。

但是有可能在SQL中做,虽然它有点复杂。

递归CTE的第一部分将采用OrderID = 1和InvID = 1并对其进行计算,从而得到LeftInLot> 0或LeftToServe> 0

第二部分现在需要基于第一部分结果的两个不同的逻辑,这是通过子查询完成下一个批次和下一个项目并使用一堆CASE来确定使用哪个 - 以及更少的CASE来提供准确的数据下一次递归继续。

看起来像这样:

;WITH CTE_Orders AS 
(
    SELECT *, ROW_NUMBER() OVER (PARTITION BY Item ORDER BY OrderID) AS RN
    FROM dbo.Orders
)
, CTE_Inventory AS 
(
    SELECT *, ROW_NUMBER() OVER (PARTITION BY Item ORDER BY InvID) AS RN
    FROM dbo.Inventory
)
, CTE AS 
(
    SELECT  o.RN AS OrderRN,
            inv.RN AS InvRN,
            OrderID ,
            o.Item ,
            o.Quantity AS OrderedQuantity ,
            InvID ,
            Lot ,
            inv.Quantity AS InvQuantity,
            CASE WHEN inv.Quantity - o.Quantity > 0 THEN o.Quantity  ELSE inv.Quantity END AS ServedQuantity ,
            CASE WHEN inv.Quantity - o.Quantity > 0 THEN 0 ELSE o.Quantity - inv.Quantity END AS LeftToServe,
            CASE WHEN inv.Quantity - o.Quantity > 0 THEN inv.Quantity - o.Quantity ELSE 0 END AS LeftInLot
    FROM CTE_Orders o
    INNER JOIN CTE_Inventory inv ON o.Item = inv.Item
    --WHERE OrderID = 1 AND InvID = 1
    WHERE o.RN =1 AND inv.RN = 1

    UNION ALL

    SELECT  CASE WHEN c1.LeftInLot <=0 THEN c1.OrderRN ELSE c2.OrderRN END AS OrderRN
            ,CASE WHEN c1.LeftInLot <=0 THEN c2.InvRN ELSE c1.InvRN END AS InvRN
            ,CASE WHEN c1.LeftInLot <=0 THEN c1.OrderID ELSE c2.OrderID END AS OrderID
            ,CASE WHEN c1.LeftInLot <=0 THEN c1.Item ELSE c2.Item END AS Item
            ,CASE WHEN c1.LeftInLot <=0 THEN c1.OrderedQuantity ELSE  c2.OrderedQuantity END AS OrderedQuantity
            ,CASE WHEN c1.LeftInLot <=0 THEN c2.InvID ELSE c1.InvID END AS InvID
            ,CASE WHEN c1.LeftInLot <=0 THEN c2.Lot ELSE c1.Lot END AS Lot
            ,CASE WHEN c1.LeftInLot <=0 THEN c2.InvQuantity ELSE c1.LeftInLot END AS InvQuantity
            ,CASE WHEN CASE WHEN c1.LeftInLot <=0 THEN c2.InvQuantity ELSE c1.LeftInLot END - CASE WHEN c1.LeftInLot <=0 THEN c1.LeftToServe ELSE c2.OrderedQuantity END > 0
                  THEN CASE WHEN c1.LeftInLot <=0 THEN c1.LeftToServe ELSE c2.OrderedQuantity END
                  ELSE CASE WHEN c1.LeftInLot <=0 THEN c2.InvQuantity ELSE c1.LeftInLot END
             END AS ServedQuantity
            ,CASE WHEN CASE WHEN c1.LeftInLot <=0 THEN c2.InvQuantity ELSE c1.LeftInLot END - CASE WHEN c1.LeftInLot <=0 THEN c1.LeftToServe ELSE c2.OrderedQuantity END > 0
                  THEN 0
                  ELSE CASE WHEN c1.LeftInLot <=0 THEN c1.LeftToServe ELSE  c2.OrderedQuantity END - CASE WHEN c1.LeftInLot <=0 THEN c2.InvQuantity ELSE c1.LeftInLot END  
             END AS LeftToServe
            ,CASE WHEN CASE WHEN c1.LeftInLot <=0 THEN c2.InvQuantity ELSE c1.LeftInLot END - CASE WHEN c1.LeftInLot <=0 THEN c1.LeftToServe ELSE c2.OrderedQuantity END > 0
                  THEN  CASE WHEN c1.LeftInLot <=0 THEN c2.InvQuantity ELSE c1.LeftInLot END - CASE WHEN c1.LeftInLot <=0 THEN c1.LeftToServe ELSE  c2.OrderedQuantity END 
                  ELSE 0 
             END AS LeftInLot
    FROM CTE c1
    INNER JOIN 
    (
        SELECT  o2.RN AS OrderRN,
                inv2.RN AS InvRN,
                InvID ,
                Lot ,
                inv2.Item ,
                inv2.Quantity AS InvQuantity,
                OrderID ,
                o2.Quantity AS OrderedQuantity
        FROM 
        CTE_Inventory inv2 
        INNER JOIN CTE_Orders o2 ON inv2.Item = o2.Item
    ) c2
    ON c1.Item = c2.Item AND
    ((c2.InvRN = c1.InvRN + 1 AND c2.OrderRN = c1.OrderRN AND c1.LeftInLot <= 0 ) OR (c2.OrderRN = c1.OrderRN + 1 AND c2.InvRN = c1.InvRN AND c1.LeftInLot>0))

)
SELECT * FROM CTE
ORDER BY item,OrderID

SQLFiddle DEMO - old

<强> SQLFiddle DEMO - fixed

PS:因为你不能真正依赖OrderIDInvID是连续的值而没有任何空白 - 就像我的例子所假设的那样(c2.OrderID = c1.OrderID + 1),另外一个并发症ROW_NUMBER应该完成。(已修复)

修改

更新了解决方案以处理多个项目。开始时有几个CTE计算为项目分区的ROW_NUMBERS,并在JOIN中使用它们而不是ID。