Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CTE - recursively update quantity until total consumed

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.

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:

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

The inventory table shows that we have plenty of item #1 on hand for account 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:

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.

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.

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

like image 796
Brian Avatar asked Oct 20 '11 22:10

Brian


People also ask

How do I limit recursion on CTE?

You can define the maximum number of recursions for CTE, using the MAXRECURSION option. Set the value of MAXRECURSION to 0, if you don't know the exact numbers of recursions.

Can CTE be recursive till what level it can be nested?

And these recursive functions or stored procedures support only up-to 32 levels of recursion. By default CTEs support a maximum recursion level of 100. CTEs also provide an option to set a MAXRECURSION level value between 0 to 32,767.

What is considered to be a disadvantage of using recursive common table expressions?

Disadvantages of CTEThe CTE can only be referenced once by the Recursive member. We cannot use the table variables and CTEs as parameters in stored procedures. We already know that the CTE could be used in place of a view, but a CTE cannot be nested, while Views can.

What is the maximum level up to which the stored procedure can be recursive?

A stored procedure can call itself up to the maximum nesting level of 32. This is referred to as recursion.


1 Answers

--@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;
like image 89
Bogdan Sahlean Avatar answered Sep 23 '22 11:09

Bogdan Sahlean