微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

获取订单状态,可以减少库存中的交货数量,而无需使用Curson或While循环

如何解决获取订单状态,可以减少库存中的交货数量,而无需使用Curson或While循环

就像任何零售业务一样,我们都有一个Orders表和一个库存表。我想做的是检查有足够库存可发送的订单。我需要考虑的几件事:

  1. 如果一个订单中的所有项目均可用,则认为该订单是“可交付的”

  2. OrderIDint值)的顺序检查订单的可交付状态,即OrderID = 1然后是2,依此类推。

  3. 在检查下一个订单的可交付性之前,请减少下一个订单的可用库存(不更新库存表,而只是考虑先前订单已消耗的库存数量)。

  4. 如果我们的库存不足以容纳订单中的1个或多个项目,请完全忽略该订单,并且不要减少下一个要检查的订单的可用库存量。

在以下示例中:

  1. Order = 100可以完全交付,因为我们有足够的库存来容纳所有产品。
  2. Order = 200不能完全交付,因为PID 2需要数量5,但是在100被订单100消耗之后,我们只剩下3个了
  3. 最后,Order = 300也可以完全交付,因为我们有足够的库存来存放所有产品。

测试数据

INSERT INTO @Inventory (PID,Qty)
VALUES  (1,10),(2,5),(3,2)


INSERT INTO @Order (OrderID,PID,Qty)
VALUES  (100,1,2)   --\,(100,2,2)   ----> This order is fully available,3,1)   --/,(200,5)   ----> This order is not fully available,1)   --/     because of PID 2 only 3 QTY left,(300,1);  --/

预期输出

OrderID Status
------------------------
100     Deliverable
200     NOT Deliverable
300     Deliverable

我的尝试:我知道这与实际解决方案相去甚远,但我仍想分享自己一直在尝试的方法:)

WITH OrderCTE AS 
(
    SELECT 
        DENSE_RANK() OVER (ORDER BY OrderID) AS OrderRN,OrderID,Qty
    FROM 
        @Order
),CTE AS
(
    SELECT 
        o.OrderID,o.PID,o.Qty,i.Qty - o.Qty AS QtyAvailable,o.OrderRN  AS OrderRN
    FROM
        OrderCTE o
    INNER JOIN 
        @Inventory i ON i.PID = o.PID
    WHERE 
        o.OrderID IN (SELECT TOP 1 o.OrderID
                      FROM @Order o
                      WHERE NOT EXISTS (SELECT 1 FROM @Inventory i 
                                        WHERE i.PID = o.PID AND i.Qty < o.Qty)
                      ORDER BY o.OrderID)   

    UNION ALL   

    SELECT 
        o.OrderID,o.Qty - c.QtyAvailable,c.OrderRN + 1
    FROM
        OrderCTE o
    INNER JOIN 
        @Inventory i ON i.PID = o.PID
    INNER JOIN 
        CTE c ON c.OrderRN + 1 = o.OrderRN AND c.PID = o.PID
    WHERE 
        o.Qty <= c.QtyAvailable
)
SELECT * 
FROM CTE

解决方法

以下方法无法产生正确的结果。当我将所有片段放在一起时,我得到了:

+---------+--------------------+
| OrderID | OrderIsDeliverable |
+---------+--------------------+
|     100 |                  1 |
|     200 |                  0 |
|     300 |                  0 |
+---------+--------------------+

Order=300被标记为不可交付,因为我的查询独立处理所有产品,这是不正确的。尽管该Order=200不能整体交付(基于PID=3以外的产品),但先前的Order=200却占用了PID=3的数量,并且不会影响以下订单。但这确实影响了以下顺序,这是不正确的。

我看不到如何在没有显式循环的情况下编写单个查询。

A。


您可以使用递归CTE模拟循环。

我将向您展示一个执行核心任务的查询,其余部分留给您,因为总体而言这太长了。

主要思想-您需要运行总计在达到阈值时重置。关于这个主题有很多问题,我以this作为答案的依据。

在下面的查询中,我仅查看一部分数据,仅查看一个特定的PID = 2

CTE_RN为我们提供了要迭代的行号。 CTE_Recursive是检查循环总数是否超过限制的主循环。如果是这样,它将丢弃该订单中的Qty并设置OrderIsDeliverable标志。

查询

WITH
CTE_RN
AS
(
    SELECT
        O.OrderID,O.PID,O.Qty,I.Qty AS LimitQty,ROW_NUMBER() OVER (ORDER BY O.OrderID) AS rn
    FROM
        @Order AS O
        INNER JOIN @Inventory AS I ON I.PID = O.PID
    WHERE O.PID = 2 -- this would become a parameter
),CTE_Recursive
AS
(
    SELECT
        CTE_RN.OrderID,CTE_RN.PID,CTE_RN.Qty,CTE_RN.LimitQty,CTE_RN.rn
        -- this would generate a simple running total
        --,CTE_RN.Qty AS SumQty

        -- the very first order may exceed the limit,CASE WHEN CTE_RN.Qty > CTE_RN.LimitQty
        THEN 0
        ELSE CTE_RN.Qty
        END AS SumQty,CASE WHEN CTE_RN.Qty > CTE_RN.LimitQty
        THEN 0
        ELSE 1
        END AS OrderIsDeliverable
    FROM
        CTE_RN
    WHERE
        CTE_RN.rn = 1

    UNION ALL

    SELECT
        CTE_RN.OrderID,CTE_RN.Qty + CTE_Recursive.SumQty AS SumQty

        -- check if running total exceeds the limit,CASE WHEN CTE_RN.Qty + CTE_Recursive.SumQty > CTE_RN.LimitQty
        THEN CTE_Recursive.SumQty -- don't increase the running total
        ELSE CTE_RN.Qty + CTE_Recursive.SumQty
        END AS SumQty,CASE WHEN CTE_RN.Qty + CTE_Recursive.SumQty > CTE_RN.LimitQty
        THEN 0
        ELSE 1
        END AS OrderIsDeliverable
    FROM
        CTE_RN
        INNER JOIN CTE_Recursive ON CTE_Recursive.rn + 1 = CTE_RN.rn
)
SELECT * FROM CTE_Recursive
;

结果

+---------+-----+-----+----------+----+--------+--------------------+
| OrderID | PID | Qty | LimitQty | rn | SumQty | OrderIsDeliverable |
+---------+-----+-----+----------+----+--------+--------------------+
|     100 |   2 |   2 |        5 |  1 |      2 |                  1 |
|     200 |   2 |   5 |        5 |  2 |      2 |                  0 |
|     300 |   2 |   2 |        5 |  3 |      4 |                  1 |
+---------+-----+-----+----------+----+--------+--------------------+

现在,您需要为每个PID运行此查询。我会将这个查询包装到带有参数的table-valued function中,并传递PID作为参数。也许您也可以不使用任何功能。显然,要创建一个没有表变量的函数,您需要在函数中引用实际的表,因此请相应地调整代码。

然后将其命名为:

SELECT
    ...
FROM
    @Inventory AS I
    CROSS APPLY dbo.MyFunc(I.PID) AS A

这将返回与@Order表中相同的行数。然后,您需要按OrderID对其进行分组,并查看OrderIsDeliverable标志。如果某个订单的该标志至少为0,则该订单无法交付。

类似这样的东西:

SELECT
    A.OrderID,MIN(OrderIsDeliverable) AS OrderIsDeliverable
FROM
    @Inventory AS I
    CROSS APPLY dbo.MyFunc(I.PID) AS A
GROUP BY
    A.OrderID
;

理想情况下,您应该尝试各种方法(游标,递归CTE等),确保您拥有适当的索引,测量它们在实际数据和硬件上的性能,并决定使用哪种方法。

,

编辑: 因为我雄心勃勃,所以我现在也找到了CTE解决方案。如果发现任何错误或不正确的结果,请提供我的反馈。我的旧游标解决方案如下。

带有CTE的新代码:

DECLARE @OrderQty TABLE
   (OrderID INT NOT NULL,PID INT NOT NULL,CountOfOrder INT NOT NULL,StockQty INT NOT NULL,Qty INT NOT NULL,DeliverableOrderQty INT NOT NULL,PRIMARY KEY CLUSTERED(OrderID,PID))

INSERT INTO @OrderQty
     (OrderID,PID,CountOfOrder,StockQty,Qty,DeliverableOrderQty) 
SELECT o.OrderID,o.PID,foo.CountOfOrder,foo.StockQty,o.Qty,foo.StockQty / IIF(o.Qty = 0,1,o.Qty) AS DeliverableOrderQty
FROM   @Order AS o
INNER JOIN (SELECT o.PID,COUNT(DISTINCT o.OrderID) AS CountOfOrder,i.Qty AS StockQty,SUM(o.Qty) AS TotalOrderOty
         FROM   @Order AS o
         INNER JOIN @Inventory AS i ON o.PID = i.PID
         GROUP BY o.PID,i.Qty) AS foo ON o.PID = foo.PID

DECLARE @OrdersDeliverableQty TABLE
   (OrderID INT NOT NULL PRIMARY KEY,DeliverableQty INT NOT NULL)

INSERT INTO @OrdersDeliverableQty
     (OrderID,DeliverableQty) 
SELECT oq.OrderID,oq.CountOfOrder,MIN(oq.DeliverableOrderQty) AS DeliverableQty
FROM   @OrderQty AS oq
GROUP BY oq.OrderID,oq.CountOfOrder

DECLARE @AllOrders TABLE
   (OrderID INT NOT NULL PRIMARY KEY)

INSERT INTO @AllOrders
     (OrderID) 
SELECT o.OrderID
FROM   @Order AS o
GROUP BY o.OrderID

DECLARE @DeliverableOrder TABLE
   (OrderID INT NOT NULL PRIMARY KEY);

WITH CTE_1(RankID,OrderID,Qty)
    AS (SELECT RANK() OVER(
            ORDER BY oq.PID,oq.DeliverableOrderQty DESC,oq.Qty,oq.OrderID) AS RankID,oq.OrderID,oq.PID,oq.StockQty,oq.Qty
       FROM   @OrderQty AS oq
       INNER JOIN @OrdersDeliverableQty AS ohmttoq ON oq.OrderID = ohmttoq.OrderID
                                            AND oq.DeliverableOrderQty = ohmttoq.DeliverableQty),CTE_2(MinRankID,MaxRankID)
    AS (SELECT MIN(c.RankID) AS MinRankID,MAX(c.RankID) AS MaxRankID
       FROM   CTE_1 AS c),CTE_3(NextRankID,MaxRankID,RankID,RestQty,Qty)
    AS (SELECT c2.MinRankID + 1 AS NextRankID,c2.MaxRankID AS MaxRankID,c.RankID,c.OrderID,c.PID,c.StockQty,c.StockQty - c.Qty AS RestQty,c.Qty
       FROM     CTE_1 AS c
       INNER JOIN CTE_2 AS c2 ON c.RankID = c2.MinRankID
       UNION ALL
       SELECT c3.NextRankID + 1 AS NextRankID,c3.MaxRankID,c3.NextRankID,c1.OrderID,c1.PID,c1.StockQty,CASE
                   WHEN c3.PID = C1.PID
                   THEN c3.RestQty
                ELSE c1.StockQty
            END - c1.Qty AS RestQty,c1.Qty
       FROM   CTE_3 AS c3
       INNER JOIN CTE_1 AS c1 ON c3.NextRankID = c1.RankID
       WHERE  c3.NextRankID <= c3.MaxRankID)
    INSERT INTO @DeliverableOrder
         (OrderID) 
    SELECT c.OrderID
    FROM   CTE_3 AS c
    WHERE  c.RestQty >= 0

SELECT ao.OrderID,CASE
            WHEN oo.OrderID IS NULL
            THEN 'NOT Deliverable'
        ELSE 'Deliverable'
     END AS STATUS
FROM   @AllOrders AS ao
LEFT JOIN @DeliverableOrder AS oo ON ao.OrderID = oo.OrderID

测试数据:

DECLARE @Inventory TABLE
   (PID INT NOT NULL PRIMARY KEY,Qty INT NOT NULL)

DECLARE @Order TABLE
   (OrderID INT NOT NULL,PID))

INSERT INTO @Inventory
     (PID,Qty) 
VALUES (1,10),(2,6),(3,5)

INSERT INTO @Order
     (OrderID,Qty) 
VALUES (100,2),(100,2,3,(200,5),1),(300,0),(400,(500,(600,(700,1)

结果:

OrderID Status
100     Deliverable
200     NOT Deliverable
300     Deliverable
400     NOT Deliverable
500     NOT Deliverable
600     Deliverable
700     Deliverable

如果您需要更多信息或解释,请发表评论。

带有光标的旧代码:

DECLARE @OrderQty TABLE
   (OrderID INT NOT NULL,PID))

INSERT INTO @OrderQty
   (OrderID,DeliverableQty INT NOT NULL)

INSERT INTO @OrdersDeliverableQty
   (OrderID,oq.CountOfOrder

DECLARE @AllOrders TABLE
   (OrderID INT NOT NULL PRIMARY KEY)

INSERT INTO @AllOrders
      (OrderID) 
SELECT o.OrderID
FROM   @Order AS o
GROUP BY o.OrderID

DECLARE @DeliverableOrder TABLE
   (OrderID INT NOT NULL PRIMARY KEY)


DECLARE @OrderID INT,@PID INT,@StockQty INT,@Qty INT

DECLARE @LastPIDCursor INT

DECLARE @QtyRest INT

DECLARE order_qty_cursor CURSOR
FOR SELECT oq.OrderID,oq.Qty
   FROM   @OrderQty AS oq
   INNER JOIN @OrdersDeliverableQty AS ohmttoq ON oq.OrderID = ohmttoq.OrderID
                                        AND oq.DeliverableOrderQty = ohmttoq.DeliverableQty
   ORDER BY oq.PID,oq.Qty


OPEN order_qty_cursor

FETCH NEXT FROM order_qty_cursor INTO @OrderID,@PID,@StockQty,@Qty

WHILE @@Fetch_Status = 0
   BEGIN
      IF @LastPIDCursor IS NULL
        OR @LastPIDCursor <> @PID
         BEGIN
            SET @QtyRest = @StockQty - @Qty
      END
         ELSE
         BEGIN
            SET @QtyRest = @QtyRest - @Qty
      END

      IF @QtyRest >= 0
        AND NOT EXISTS (SELECT 1
                     FROM   @DeliverableOrder
                     WHERE  OrderID = @OrderID) 
         BEGIN
            INSERT INTO @DeliverableOrder
                  (OrderID) 
            VALUES
                  (@OrderID) 
      END

      SET @LastPIDCursor = @PID

      FETCH NEXT FROM order_qty_cursor INTO @OrderID,@Qty
   END

CLOSE order_qty_cursor

DEALLOCATE order_qty_cursor

SELECT ao.OrderID,CASE
            WHEN oo.OrderID IS NULL
            THEN 'NOT Deliverable'
        ELSE 'Deliverable'
     END AS STATUS
FROM   @AllOrders AS ao
LEFT JOIN @DeliverableOrder AS oo ON ao.OrderID = oo.OrderID

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。