SQL Server 2012 - 根据另一列的值重置运行总计

标签 sql t-sql sql-server-2012 recursive-query cumulative-sum

当每周数量值达到或超过最小批量时,我正在使用 SQL Server 2012 尝试设置新的预测目标。我知道这需要一个运行总计,并且还需要一种方法来标记需要重置运行总计的行。

目前正在尝试递归解决方案,但我也可以使用窗口方法。谢谢!

CREATE TABLE dbo.Table_1
(
    ITEM_ID nvarchar(20) NOT NULL,
    DUE_DATE datetime NOT NULL,
    MIN_LOT_SIZE int NOT NULL,
    QUANTITY int NOT NULL
)

INSERT INTO dbo.Table_1 (ITEM_ID, DUE_DATE, MIN_LOT_SIZE, QUANTITY) 
VALUES
('ITEM_1', '1/4/2021', 460, 102)
,('ITEM_1', '1/11/2021', 460, 101)
,('ITEM_1', '2/8/2021', 460, 100)
,('ITEM_1', '3/8/2021', 460, 101)
,('ITEM_1', '4/5/2021', 460, 141)
,('ITEM_1', '5/10/2021', 460, 142)
,('ITEM_1', '6/7/2021', 460, 142)
,('ITEM_1', '7/5/2021', 460, 142)
,('ITEM_1', '8/2/2021', 460, 142)
,('ITEM_1', '9/6/2021', 460, 142)
,('ITEM_1', '10/4/2021', 460, 142)
,('ITEM_1', '11/1/2021', 460, 142)
,('ITEM_2', '10/4/2021', 5000, 1057)
,('ITEM_2', '11/1/2021', 5000, 1422)
,('ITEM_3', '1/4/2021', 3050, 17)
,('ITEM_3', '1/11/2021', 3050, 761)
,('ITEM_3', '2/1/2021', 3050, 752)
,('ITEM_3', '3/1/2021', 3050, 760)
,('ITEM_3', '4/5/2021', 3050, 1059)
,('ITEM_3', '5/3/2021', 3050, 1066)
,('ITEM_3', '6/7/2021', 3050, 1066)
,('ITEM_3', '7/5/2021', 3050, 1066)
,('ITEM_3', '8/2/2021', 3050, 1061)
,('ITEM_3', '9/6/2021', 3050, 1066)
,('ITEM_3', '10/4/2021', 3050, 1061)
,('ITEM_3', '11/1/2021', 3050, 1066)

下面我当前的代码适用于 ITEM_1 来标记正确的行,但随后的 ITEM_1 运行总计会在第二个标记之后进入 ITEM_2,并且以后所有项目都会出现这种情况。

查询:

WITH cte AS 
(
    SELECT 
        rn, item_id, due_date, min_lot_size, quantity, running_total, flag 
    FROM 
        (SELECT 
             ROW_NUMBER() OVER (ORDER BY item_id, due_date) AS rn,
             item_id,
             due_date,
             min_lot_size,
             quantity,
             SUM(quantity) OVER (PARTITION BY item_id ORDER BY due_date ASC, quantity DESC ROWS UNBOUNDED PRECEDING) AS running_total,
             CASE 
                WHEN SUM(quantity) OVER (PARTITION BY item_id ORDER BY due_date ASC, quantity DESC ROWS UNBOUNDED PRECEDING) >= min_lot_size 
                   THEN 1 
                   ELSE 0 
             END AS flag
         FROM 
             Table_1) A
     WHERE 
         rn = 1
     UNION ALL
     SELECT 
         r.rn, r.item_id, r.due_date, r.min_lot_size, r.quantity,
         CASE c.flag
            WHEN 1 THEN r.quantity
            ELSE c.running_total + r.quantity
         END,
         CASE 
            WHEN 
               CASE c.flag
                  WHEN 1 THEN r.quantity
                  ELSE c.running_total + r.quantity
               END > c.min_lot_size 
               THEN 1 ELSE 0 
         END
     FROM   
         cte c
     JOIN 
         (SELECT 
              ROW_NUMBER() OVER (ORDER BY item_id, due_date) AS rn, 
              item_id, due_date, min_lot_size, quantity 
          FROM Table_1) r ON r.rn = c.rn + 1
)
SELECT *
FROM cte 
ORDER BY rn
OPTION (maxrecursion 0)

期望的输出:

Desired Output:

最佳答案

对于这样的问题,您必须获得有关运行结果的真正元数据(一行的运行结果不取决于先前的值,而是取决于先前的运行结果)。当 SQL Server 以与 PostgreSql 等其他实现不同且不太直观的方式处理窗口函数时,我想不出在 CTE 内执行此操作的方法。如果它违反了标准,它甚至可能被认为是有缺陷的。

我下面给出的策略使用带有 while 循环的递归。但它不是 RBAR,所以我认为与您可能担心的这样的循环相比,性能不会很糟糕。事实上,如果窗口函数在 SQL Server 的递归部分内工作,我将其构造为类似于递归 CTE 的语法。顺便说一句,我实际上在 postgreSQL 中将其实现为递归 CTE,并取得了成功。

对于循环要记住的一件事是我让你的“标志”变得更复杂一些。我将我的标志称为“已处理”,它可以采用三个值:1、-1 和 0。0 表示“未处理”,1 表示“已处理”,-1 也已处理,但它是一个特殊标记作为您的“旗帜”。

-- The Anchor Part
select  *, 
        result = 0,
        processed = 0
into    #results
from    Table_1

-- The Recursive Part
while exists (select 0 from #results where processed = 0)

    update  r
    set     r.result = r.runSum,
            r.processed = 
                case 
                when r.runSum <= r.min_lot_size then 1
                -- first entry that is greater than min_lot_size
                when r.runSum - quantity < r.min_lot_size then -1 
                else 0
                end
    from    (
                select  *,
                        runSum = sum(quantity) over(
                            partition by item_id 
                            order by due_date
                        )
                from    #results
                where   processed = 0
            ) r;

-- Final Output
select  ITEM_ID, 
        DUE_DATE, 
        MIN_LOT_SIZE, 
        QUANTITY, 
        NEW_QUANTITY = iif(processed = -1, result, 0)
from    #results;
  • 这是 SQL Server 中的可运行结果:sql server .
  • 这是我上面讨论的真正的递归 CTE:postgre .

关于SQL Server 2012 - 根据另一列的值重置运行总计,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65330584/

相关文章:

sql - Azure SQL - 空间性能问题

mysql - 如何在与另一个表连接之前找到一组数据的模式?

sql - 使用带有 MAX() 的 GROUP BY 作为聚合与使用 ROW_NUMBER 进行分区相比,是否存在性能差异?

sql - 将多个表插入到一张表中

sql - 将数据从 SQL Server 2012 复制到 Excel 时出现换行问题

sql-server - 限制可以在列中输入唯一值的次数

mysql - 如何获取没有特定相关实体的所有实体?

sql-server - 具有多个 JOINS 的复杂选择逻辑

sql - 在存储过程中为同一变量传递多个值

java - 使用 SQL Server sp_send_dbmail 从 java 发送电子邮件