CTE - 递归更新数量直到总消耗量

时间:2022-10-19 07:20:41

I've been researching CTEs trying to determine if it's possible to recursively update inventory quantity records with an order quantity until the order quantity is consumed.

我一直在研究CTE,试图确定是否可以用订单数量递归更新库存数量记录,直到订单数量被消耗。

Here are the tables and records:

以下是表格和记录:

CREATE TABLE [dbo].[myOrder](
  [Account] [float] NOT NULL,
  [Item] [float] NOT NULL,
  [Quantity] [float] NOT NULL
) ON [PRIMARY]

insert into dbo.myOrder values (12345, 1, 50)

CREATE TABLE [dbo].[myInventory](
  [ID] [int] IDENTITY(1,1) NOT NULL,
  [Account] [float] NOT NULL,
  [InvDate] [numeric](18, 0) NOT NULL,
  [Item] [float] NOT NULL,
  [Quantity] [float] NOT NULL,
  [QuantitySold] [float] NOT NULL
) ON [PRIMARY]

insert into dbo.myInventory values (12345, 111287, 1, 45, 40)
insert into dbo.myInventory values (12345, 111290, 1, 40, 0)
insert into dbo.myInventory values (12345, 111290, 1, 12, 0)
insert into dbo.myInventory values (12345, 111291, 1, 25, 0)

The record in the myOrder table indicates that an order is to be created for account 12345 for item #1, quantity 50:

myOrder表中的记录表示要为项目#1创建一个订单12345,数量为50:

Account Item Quantity 
------- ---- --------
12345   1    50

The inventory table shows that we have plenty of item #1 on hand for account 12345:

库存表显示我们手头有足够的第1项帐户12345:

ID Account InvDate Item Quantity QuantitySold
-- ------- ------- ---- -------- ------------
1  12345   111287  1    45       40
2  12345   111290  1    40       0
3  12345   111290  1    12       0
4  12345   111291  1    25       0

The goal is to start plugging in the order quantity of 50 into the inventory records until all 50 are consumed. Inventory records are ordered by the value in the InvDate column. Record 1 has 5 remaining quantity (45 - 40 = 5), which would leave us with 45 more to consume for the order. Record 2 can consume 40. Record 3 can consume the last 5. When the query completes the inventory records would look like this:

目标是开始将订单数量50插入库存记录,直到所有50个消耗完为止。库存记录按InvDate列中的值排序。记录1有5个剩余数量(45 - 40 = 5),这将为我们留下45个以上的订单。记录2可以消耗40.记录3可以消耗最后一个5.当查询完成时,库存记录将如下所示:

ID Account InvDate Item Quantity QuantitySold
-- ------- ------- ---- -------- ------------
1  12345   111287  1    45       45
2  12345   111290  1    40       40
3  12345   111290  1    12       5
4  12345   111291  1    25       0

Note: The inventory table stores QuantitySold, not QuantityRemaining, so you have to do the math (Quantity - QuantitySold) to determine how much quantity remains per inventory record.

注意:库存表存储QuantitySold,而不是QuantityRemaining,因此您必须进行数学运算(Quantity - QuantitySold)以确定每个库存记录剩余的数量。

I've gotten almost nowhere with the CTE. I've found plenty of examples for doing selects where you have 2 parts to your CTE - an initialization part and the recursive part UNIONed together. I could write this with a cursor, but I think it's possible to do with a CTE and I'd like to learn how.

我在CTE几乎无处可去。我已经找到了很多做选择的例子,你的CTE有2个部分 - 初始化部分和UNIONed的递归部分。我可以用光标写这个,但我认为可以用CTE做,我想学习如何。

If anyone can confirm this is possible with a CTE or explain how to set up the CTE, I'd appreciate it. Thanks!

如果有人可以通过CTE确认这是可能的,或者解释如何设置CTE,我会很感激。谢谢!

1 个解决方案

#1


10  

--@inserted table mimics inserted virtual table from AFTER INSERT triggers on [dbo].[myOrder] table
DECLARE @inserted TABLE 
(
  [Account] [float] NOT NULL,
  [Item] [float] NOT NULL,
  [Quantity] [float] NOT NULL
);

INSERT  @inserted 
VALUES  (12345, 1, 50);

WITH CteRowNumber
AS
(
    SELECT   inv.ID
            ,inv.Account
            ,inv.Item
            ,inv.Quantity
            ,inv.QuantitySold
            ,i.Quantity QuantityOrdered
            ,ROW_NUMBER() OVER(PARTITION BY inv.Account,inv.Item ORDER BY inv.ID ASC) RowNumber
    FROM    myInventory inv
    INNER JOIN @inserted i ON inv.Account = i.Account 
    AND     inv.Item = i.Item 
    WHERE   inv.Quantity > inv.QuantitySold
),  CteRecursive
AS
(
    SELECT   a.ID
            ,a.Account
            ,a.Item
            ,a.RowNumber 
            ,CASE 
                WHEN a.Quantity - a.QuantitySold < a.QuantityOrdered THEN a.Quantity - a.QuantitySold 
                ELSE a.QuantityOrdered
            END QuantitySoldNew
            ,CASE 
                WHEN a.Quantity - a.QuantitySold < a.QuantityOrdered THEN a.Quantity - a.QuantitySold 
                ELSE a.QuantityOrdered
            END RunningTotal
    FROM    CteRowNumber a
    WHERE   a.RowNumber = 1
    UNION ALL
    SELECT   crt.ID
            ,crt.Account
            ,crt.Item
            ,crt.RowNumber
            ,CASE 
                WHEN prev.RunningTotal + (crt.Quantity - crt.QuantitySold) < crt.QuantityOrdered THEN crt.Quantity - crt.QuantitySold
                ELSE crt.QuantityOrdered - prev.RunningTotal
            END QuantitySoldNew
            ,CASE 
                WHEN prev.RunningTotal + (crt.Quantity - crt.QuantitySold) < crt.QuantityOrdered THEN prev.RunningTotal + (crt.Quantity - crt.QuantitySold)
                ELSE crt.QuantityOrdered
            END RunningTotal
    FROM    CteRecursive prev
    INNER JOIN CteRowNumber crt ON prev.Account = crt.Account 
    AND     prev.Item = crt.Item 
    AND     prev.RowNumber + 1 = crt.RowNumber
    WHERE   prev.RunningTotal  < crt.QuantityOrdered
)
SELECT   cte.ID
        ,cte.Account
        ,cte.Item
        ,cte.QuantitySoldNew
FROM    CteRecursive cte;
--or CteRecursive can be used to update QuantitySold column from [dbo].[myInventory] table
--UPDATE    myInventory 
--SET       QuantitySold = inv.QuantitySold + cte.QuantitySoldNew
--FROM  myInventory inv
--INNER JOIN CteRecursive cte ON inv.ID = cte.ID;

#1


10  

--@inserted table mimics inserted virtual table from AFTER INSERT triggers on [dbo].[myOrder] table
DECLARE @inserted TABLE 
(
  [Account] [float] NOT NULL,
  [Item] [float] NOT NULL,
  [Quantity] [float] NOT NULL
);

INSERT  @inserted 
VALUES  (12345, 1, 50);

WITH CteRowNumber
AS
(
    SELECT   inv.ID
            ,inv.Account
            ,inv.Item
            ,inv.Quantity
            ,inv.QuantitySold
            ,i.Quantity QuantityOrdered
            ,ROW_NUMBER() OVER(PARTITION BY inv.Account,inv.Item ORDER BY inv.ID ASC) RowNumber
    FROM    myInventory inv
    INNER JOIN @inserted i ON inv.Account = i.Account 
    AND     inv.Item = i.Item 
    WHERE   inv.Quantity > inv.QuantitySold
),  CteRecursive
AS
(
    SELECT   a.ID
            ,a.Account
            ,a.Item
            ,a.RowNumber 
            ,CASE 
                WHEN a.Quantity - a.QuantitySold < a.QuantityOrdered THEN a.Quantity - a.QuantitySold 
                ELSE a.QuantityOrdered
            END QuantitySoldNew
            ,CASE 
                WHEN a.Quantity - a.QuantitySold < a.QuantityOrdered THEN a.Quantity - a.QuantitySold 
                ELSE a.QuantityOrdered
            END RunningTotal
    FROM    CteRowNumber a
    WHERE   a.RowNumber = 1
    UNION ALL
    SELECT   crt.ID
            ,crt.Account
            ,crt.Item
            ,crt.RowNumber
            ,CASE 
                WHEN prev.RunningTotal + (crt.Quantity - crt.QuantitySold) < crt.QuantityOrdered THEN crt.Quantity - crt.QuantitySold
                ELSE crt.QuantityOrdered - prev.RunningTotal
            END QuantitySoldNew
            ,CASE 
                WHEN prev.RunningTotal + (crt.Quantity - crt.QuantitySold) < crt.QuantityOrdered THEN prev.RunningTotal + (crt.Quantity - crt.QuantitySold)
                ELSE crt.QuantityOrdered
            END RunningTotal
    FROM    CteRecursive prev
    INNER JOIN CteRowNumber crt ON prev.Account = crt.Account 
    AND     prev.Item = crt.Item 
    AND     prev.RowNumber + 1 = crt.RowNumber
    WHERE   prev.RunningTotal  < crt.QuantityOrdered
)
SELECT   cte.ID
        ,cte.Account
        ,cte.Item
        ,cte.QuantitySoldNew
FROM    CteRecursive cte;
--or CteRecursive can be used to update QuantitySold column from [dbo].[myInventory] table
--UPDATE    myInventory 
--SET       QuantitySold = inv.QuantitySold + cte.QuantitySoldNew
--FROM  myInventory inv
--INNER JOIN CteRecursive cte ON inv.ID = cte.ID;