有效计算连续行之间的增量之和的方法?

时间:2009-08-13 02:31:13

标签: sql tsql

我有一个由时间戳值和绝对值(米)组成的数据集。有时仪表值会重置为零,这意味着我必须迭代并逐个计算一个增量,然后将其相加以获得给定时间段内的总和。

例如:

Timestamp      Value
2009-01-01     100
2009-01-02     105
2009-01-03     120
2009-01-04     0 
2009-01-05     9

此处的总数为29,计算公式为:

(105 - 100) + (120 - 105) + (0) + (9 - 0) = 29

我正在使用MS-SQL服务器,并接受任何建议。

现在,我正在使用游标执行此操作,检查delta是否为负数,然后将其合计:

DECLARE CURSOR curTest CURSOR FAST_FORWARD FOR
    SELECT value FROM table ORDER BY timestamp
OPEN curTest
DECLARE @delta bigint, @current bigint, @last bigint
SET @delta = 0

FETCH curTest INTO @current
WHILE @@FETCH_STATUS = 0
BEGIN
    IF (@current IS NOT NULL) AND (@current > 0) 
    BEGIN
        IF (@last IS NOT NULL) AND (@current > @last)
            SET @delta = @delta + (@current - @last)
        SET @last = @current

        FETCH curTest INTO @current
    END
END

CLOSE curTest
DEALLOCATE curTest

获得如下数据集会很好:

Timestamp      Value    LastValue
2009-01-01     100      NULL
2009-01-02     105      100
2009-01-03     120      105
2009-01-04     0        120
2009-01-05     9        0

因为很容易获取增量,过滤(Value> LastValue),然后执行SUM()。

我试过了:

SELECT m1.timestamp, m1.value, 
  ( SELECT TOP 1 m2.value FROM table WHERE m2.timestamp < m1.timestamp ORDER BY m2.timestamp DESC ) as LastValue
FROM table 

但事实证明这比光标慢:当我在SQL studio中使用'show execution plan'一起运行时,相对成本是100%(有7或8次操作 - 大多数是聚集索引扫描时间戳),光标为0%(有3个操作)。

(为简单起见,我在这里没有展示的是我有几组不同的数字,在这个表中也有一个外键 - 所以总有一个WHERE子句限制到一个特定的集合。我有几个我一次为几个集合计算给定时间段内这些总计的地方,因此它变成了性能瓶颈。非游标方法也可以很容易地修改为GROUP BY键并立即返回所有集合 - 但是这实际上在我的测试中比多次运行游标更慢,因为除了总体上更慢之外,还有GROUP BY和SUM()操作的额外开销。)

3 个答案:

答案 0 :(得分:4)

大致相同......

create table #temp ([timestamp] date,value int);
insert into #temp (timestamp,value) values ('2009-01-01',100)
insert into #temp (timestamp,value) values ('2009-01-02',105)
insert into #temp (timestamp,value) values ('2009-01-03',120)
insert into #temp (timestamp,value) values ('2009-01-04',0)
insert into #temp (timestamp,value) values ('2009-01-05',9);

with numbered as
(
    select ROW_NUMBER() over (order by timestamp) id,value from #temp
)
select sum(n1.value-n2.value) from numbered n1 join numbered n2 on n1.id=n2.id+1 where n1.value!=0

drop table #temp;

结果是29,如指定的那样。

答案 1 :(得分:2)

从row_number开始,然后加入回到自己。

with numbered as
(
SELECT value, row_number() over (order by timestamp) as Rownum 
FROM table
)
select sum(n1.value - n2.value)
from numbered n1
  join
  numbered n2  on n1.Rownum = n2.Rownum +1

实际上......你只想拿起增加...所以放一个WHERE子句,说“WHERE n1.value&gt; n2.value”。

而且......确保我把它们放在正确的位置......我刚刚将它从-1改为+1,因为我认为我已将它翻转过来。

容易!

罗布

答案 2 :(得分:0)

您的算法中有太多不必要的连接。

计算每个仪表读数与其后续仪表读数之间的差异是浪费资源。作为一个现实世界的例子,想象一下,如果我的电力公司每天读取我的电表我使用了多少电量,并将每日价值相加以确定我的每月总数 - 这是没有意义的。他们只是根据起始值和结束值确定总数!

只需计算第一个和最后一个读数之间的差异,然后根据“重置”进行调整。你的公式变成了:

total value = (final value) - (initial value) 
                 + (miscellaneous reductions in value, i.e. resets)
total value = (9) - (100) + (120)
            = 29

找到最终值和初始值是微不足道的。只需找到“重置”期间“米”减少的总量,并将其添加到总数中。除非有比计量记录更多的重置记录,否则这将始终更有效。

借用消费者的解决方案,可以通过

计算“重置”值
create table...

select sum(n1.value-n2.value) from numbered n1 join numbered n2 
     on n1.id=n2.id+1 where n1.value=0  //note value=0 rather than value!=0