【问题标题】:Query with row by row calculation for running total逐行计算查询运行总计
【发布时间】:2021-10-23 13:11:27
【问题描述】:

我有一个问题,工作在一周开始时“到期”,并且每周都有一定数量的“空档”可用于完成任何未完成的工作。如果没有足够的空位,则作业将滚动到下周。

我的初始表格如下所示:

Week Slots Due
23/8/2021 0 1
30/8/2021 2 3
6/9/2021 5 2
13/9/2021 1 4

我想在每周结束时保持“到期”作业的运行总数。 每周到期的数量将被添加到上周的运行总数中,然后将减去本周的插槽数量。如果有足够的槽位来完成所需的所有作业,则运行总数将为 0(永远不会为负数)。

作为一个例子 - 下面显示了我将如何在 javascript 中实现这一点:

var Total = 0;
data.foreach(function(d){
    Total += d.Due;
    Total -= d.Slots;
    Total = Total > 0 ? Total : 0;
    d.Total = Total;
});

结果如下:

Week Slots Due Total
23/8/2021 0 1 1
30/8/2021 2 3 2
6/9/2021 5 2 0
13/9/2021 1 4 3

我是否可以在 SQL(特别是 SQL Server 2012)中实现这一点

我尝试过各种形式的sum(xxx) over (order by yyy)

我管理的最近的是:

sum(Due) over (order by Week) - sum(Slots) over (order by Week) as Total

这提供了一个运行总数,但当有多余的插槽时将提供一个负数。

这是用光标完成此操作的唯一方法吗?如果是这样 - 有什么建议吗?

谢谢。

【问题讨论】:

  • 使用递归查询遍历行。
  • “带”光标?还是“没有”光标?您应该尽可能使用基于集合的操作。
  • 您将不得不使用某种递归。 Thorsten 提到了递归查询——很好,我觉得它的语法有点陌生。光标是另一种选择,语法过于复杂。我建议使用while 声明。
  • 循环总计算 = 到期 - 插槽。如果值为负数,则它认为由于运行总计算或这背后的任何逻辑。请解释一下。
  • @Rahul Biswas:我认为 JHW 已经很好地解释了这一点。无法满足 2021 年 8 月 23 日到期的 1,因为插槽为 0。因此它添加到 2021 年 8 月 30 日的到期 3。这使得 4,但只有 2 个插槽。因此,在 2021 年 6 月 9 日,剩余的 2 个被添加到日期的 2 个到期日,即 4 个。有 5 个插槽,所以什么都没有。这是一个迭代过程,因为您不能只是将会费和插槽相加。到期 1,无论您有 1 个插槽还是 1000 个插槽都没有区别。您需要一个循环。 SQL 中的循环意味着递归查询。

标签: sql sql-server tsql common-table-expression recursive-query


【解决方案1】:

根据 cmets 中的建议对我自己的问题的可能答案。

Thorsten Kettner 建议使用递归查询:

with cte as (

select [Week], [Due], [Slots]
,case when Due > Slots then Due - Slots else 0 end as [Total]
from [Data]
where [Week] = (select top 1 [Week] from [Data])

union all

select  e.[Week], e.[Due], e.[Slots]
, case when cte.Total + e.Due - e.Slots > 0 then cte.Total + e.Due - e.Slots else 0 end as [Total]
from [Data] e
inner join cte on cte.[Week] = dateadd(day,-7,e.[Week])
)

select * from cte

OPTION (MAXRECURSION 200)

Thorsten - 这就是你的建议吗? (如果您有任何改进,请作为答案发布,以便我接受!)

大概我必须确保将 MAXRECURSION 设置为高于我将要处理的行数?

我对加入dateadd(day,-7,e.[Week]) 有点紧张。我会更好地使用 Row_Number() 来获取之前的记录吗?我可能想使用几周以外的时间,或者可能缺少几周?

George Menoutis 提出了一个“while”查询,当我看到这篇文章时,我正在寻找实现该查询的方法:https://stackoverflow.com/a/35471328/1372848

这表明与一段时间相比,光标可能没有那么糟糕?

这是我想出的基于光标的版本:

SET NOCOUNT ON;
DECLARE @Week Date,
        @Due Int,
        @Slots Int,
        @Total Int = 0;

DECLARE @Output TABLE ([Week] Date NOT NULL, Due Int NOT NULL, Slots Int NOT NULL, Total Int);

DECLARE crs CURSOR STATIC LOCAL READ_ONLY FORWARD_ONLY
FOR  SELECT [Week], Due, Slots
     FROM   [Data]
    ORDER BY [Week] ASC;

OPEN crs;

FETCH NEXT
FROM  crs
INTO  @Week, @Due, @Slots;

WHILE (@@FETCH_STATUS = 0)
BEGIN
    Set @Total = @Total + @Due;
    Set @Total = @Total - @Slots;
    Set @Total = IIF(@Total > 0, @Total , 0)

    INSERT INTO @Output ([Week], [Due], [Slots], [Total])
    VALUES (@Week, @Due, @Slots, @Total);

    FETCH NEXT
    FROM  crs
    INTO  @Week, @Due, @Slots;
END;

CLOSE crs;
DEALLOCATE crs;

SELECT *
FROM   @Output;

这两个似乎都按预期工作。递归查询感觉更好(光标 = 坏等),但它是否设计为以这种方式使用(每个输入行都有递归,因此可能有非常多的递归?)

非常感谢大家的意见:-)

【讨论】:

  • 递归查询看起来不错。不过,您对那一周的事情是正确的,并且您应该更愿意处理数字(作为第一步对行进行编号的非递归 cte,然后基于该结果进行递归 cte)。至于 MAXRECURSION´:将其设置为 0,这意味着您不需要 DBMS 来注意限制。我必须承认,我什至根本不知道 SQL Server 中为什么存在 MAXRECURSION。
【解决方案2】:

根据 Thorsten 的意见改进之前的答案

with numbered as (
select *, ROW_NUMBER() OVER (ORDER BY [Week]) as RN
from [Data]
)
,cte as (
select [Week], [Due], [Slots], [RN]
,case when Due > Slots then Due - Slots else 0 end as [Total]
from numbered
where RN = 1

union all

select  e.[Week], e.[Due], e.[Slots], e.[RN]
, case when cte.Total + e.Due - e.Slots > 0 then cte.Total + e.Due - e.Slots else 0 end as [Total]
from numbered e
inner join cte on cte.[RN] = e.[RN] - 1
)

select * from cte

OPTION (MAXRECURSION 0)

非常感谢 Thorsten 的所有帮助。

【讨论】:

    猜你喜欢
    • 2023-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-04
    • 1970-01-01
    • 2012-07-03
    • 2023-03-23
    相关资源
    最近更新 更多