Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sql-server/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
计算SQL Server中的运行总数_Sql_Sql Server_Tsql_Cumulative Sum - Fatal编程技术网

计算SQL Server中的运行总数

计算SQL Server中的运行总数,sql,sql-server,tsql,cumulative-sum,Sql,Sql Server,Tsql,Cumulative Sum,设想下一个名为TestTable的表: 我想要一个按日期顺序返回运行总数的查询,如: id somedate somevalue runningtotal -- -------- --------- ------------ 45 01/Jan/09 3 3 23 08/Jan/09 5 8 12 02/Feb/09 0 8 77 14/Feb/09 7

设想下一个名为TestTable的表:

我想要一个按日期顺序返回运行总数的查询,如:

id     somedate    somevalue  runningtotal
--     --------    ---------  ------------
45     01/Jan/09   3          3
23     08/Jan/09   5          8
12     02/Feb/09   0          8
77     14/Feb/09   7          15  
39     20/Feb/09   34         49
33     02/Mar/09   6          55
我知道在SQLServer2000/2005/2008中有很多

我对这种使用聚合集语句技巧的方法特别感兴趣:

INSERT INTO @AnotherTbl(id, somedate, somevalue, runningtotal) 
   SELECT id, somedate, somevalue, null
   FROM TestTable
   ORDER BY somedate

DECLARE @RunningTotal int
SET @RunningTotal = 0

UPDATE @AnotherTbl
SET @RunningTotal = runningtotal = @RunningTotal + somevalue
FROM @AnotherTbl
。。。这是非常有效的,但我听说这方面存在一些问题,因为您不一定能保证UPDATE语句将以正确的顺序处理行。也许我们可以得到一些关于这个问题的明确答案

但也许人们可以提出其他的建议

编辑:现在,使用上面的设置和“更新技巧”示例

SELECT TOP 25   amount, 
    (SELECT SUM(amount) 
    FROM time_detail b 
    WHERE b.time_detail_id <= a.time_detail_id) AS Total FROM time_detail a

您还可以使用ROW_NUMBER函数和临时表来创建任意列,以便在内部SELECT语句的比较中使用。

假设在SQL Server 2008上可以像我在其他地方尝试的那样使用窗口,请尝试一下:

select testtable.*, sum(somevalue) over(order by somedate)
from testtable
order by somedate;
说它在SQLServer2008中可用,也许在2005年也可用?但我手头没有一个实例来尝试它

编辑:显然SQL Server不允许在。。。不通过将结果分成多个组来指定分区,但不按照GROUP BY的方式进行聚合。恼人-MSDN语法参考表明它是可选的,但目前我只有SQLServer2000实例

我给出的查询适用于Oracle 10.2.0.3.0和PostgreSQL 8.4-beta。因此,让MS跟上进度

更新,如果您正在运行SQL Server 2012,请参阅:

问题是Over子句的SQLServer实现是错误的

Oracle和ANSI-SQL允许您执行以下操作:

 SELECT somedate, somevalue,
  SUM(somevalue) OVER(ORDER BY somedate 
     ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) 
          AS RunningTotal
  FROM Table
SQL Server没有提供解决此问题的干净解决方案。我的直觉告诉我,这是一种罕见的情况,光标是最快的,尽管我必须对大的结果做一些基准测试

更新技巧很方便,但我觉得它相当脆弱。如果您正在更新一个完整的表,那么它将按照主键的顺序进行。因此,如果您将日期设置为主键,您可能会比较安全。但是,如果查询最终由两个过程执行,那么您也依赖于一个未记录的SQL Server实现细节。我想知道会发生什么,请参见:MAXDOP:

完整工作样本:

drop table #t 
create table #t ( ord int primary key, total int, running_total int)

insert #t(ord,total)  values (2,20)
-- notice the malicious re-ordering 
insert #t(ord,total) values (1,10)
insert #t(ord,total)  values (3,10)
insert #t(ord,total)  values (4,1)

declare @total int 
set @total = 0
update #t set running_total = @total, @total = @total + total 

select * from #t
order by ord 

ord         total       running_total
----------- ----------- -------------
1           10          10
2           20          30
3           10          40
4           1           41
你要的是一个基准,这是事实真相

最快的安全方法是游标,它比交叉连接的相关子查询快一个数量级

最快的方法是更新技巧。我唯一关心的是,我不确定在所有情况下,更新都会以线性方式进行。查询中没有明确说明这一点

最后,对于生产代码,我将使用光标

测试数据:

create table #t ( ord int primary key, total int, running_total int)

set nocount on 
declare @i int
set @i = 0 
begin tran
while @i < 10000
begin
   insert #t (ord, total) values (@i,  rand() * 100) 
    set @i = @i +1
end
commit
测试4:

declare @total int 
set @total = 0
update #t set running_total = @total, @total = @total + total 

select * from #t

-- CPU 0, Reads 58, Duration 139

以下内容将产生所需的结果

SELECT a.SomeDate,
       a.SomeValue,
       SUM(b.SomeValue) AS RunningTotal
FROM TestTable a
CROSS JOIN TestTable b
WHERE (b.SomeDate <= a.SomeDate) 
GROUP BY a.SomeDate,a.SomeValue
ORDER BY a.SomeDate,a.SomeValue

在SomeDate上使用聚集索引将大大提高性能。

SQL 2005及更高版本中的APPLY运算符可用于此目的:

select
    t.id ,
    t.somedate ,
    t.somevalue ,
    rt.runningTotal
from TestTable t
 cross apply (select sum(somevalue) as runningTotal
                from TestTable
                where somedate <= t.somedate
            ) as rt
order by t.somedate

您还可以反规范化-将运行总计存储在同一个表中:


选择的工作速度比任何其他解决方案快得多,但修改速度可能较慢

我相信使用下面简单的内部联接操作可以实现运行总数

SELECT
     ROW_NUMBER() OVER (ORDER BY SomeDate) AS OrderID
    ,rt.*
INTO
    #tmp
FROM
    (
        SELECT 45 AS ID, CAST('01-01-2009' AS DATETIME) AS SomeDate, 3 AS SomeValue
        UNION ALL
        SELECT 23, CAST('01-08-2009' AS DATETIME), 5
        UNION ALL
        SELECT 12, CAST('02-02-2009' AS DATETIME), 0
        UNION ALL
        SELECT 77, CAST('02-14-2009' AS DATETIME), 7
        UNION ALL
        SELECT 39, CAST('02-20-2009' AS DATETIME), 34
        UNION ALL
        SELECT 33, CAST('03-02-2009' AS DATETIME), 6
    ) rt

SELECT
     t1.ID
    ,t1.SomeDate
    ,t1.SomeValue
    ,SUM(t2.SomeValue) AS RunningTotal
FROM
    #tmp t1
    JOIN #tmp t2
        ON t2.OrderID <= t1.OrderID
GROUP BY
     t1.OrderID
    ,t1.ID
    ,t1.SomeDate
    ,t1.SomeValue
ORDER BY
    t1.OrderID

DROP TABLE #tmp

在SQL Server 2012中,可以与子句一起使用


尽管SamSaffron在这方面做了大量工作,但他仍然没有为这个问题提供递归公共表表达式代码。对于我们这些使用SQLServer2008R2而不是Denali的人来说,它仍然是运行total的最快方法,对于100000行,它比我的工作计算机上的游标快10倍左右,而且它也是内联查询。 所以,这里我假设表中有一个ord列,它的序列号没有间隙,为了快速处理,这个数字也应该有唯一的约束:

;with 
CTE_RunningTotal
as
(
    select T.ord, T.total, T.total as running_total
    from #t as T
    where T.ord = 0
    union all
    select T.ord, T.total, T.total + C.running_total as running_total
    from CTE_RunningTotal as C
        inner join #t as T on T.ord = C.ord + 1
)
select C.ord, C.total, C.running_total
from CTE_RunningTotal as C
option (maxrecursion 0)

-- CPU 140, Reads 110014, Duration 132
更新 我还对这个带有变量或古怪更新的更新感到好奇。所以通常情况下,它可以正常工作,但我们如何确保它每次都能正常工作呢?好的,这里有一个小技巧——您只需检查当前和以前的ord,并使用1/0赋值,以防它们与您期望的不同:

declare @total int, @ord int

select @total = 0, @ord = -1

update #t set
    @total = @total + total,
    @ord = case when ord <> @ord + 1 then 1/0 else ord end,
    ------------------------
    running_total = @total

select * from #t

-- CPU 0, Reads 58, Duration 139
从我所看到的情况来看,如果您的表上有适当的聚集索引/主键,在我们的例子中,它将是按ord_id进行索引,更新将一直以线性方式进行,从未遇到过被零除的情况。也就是说,您可以决定是否要在生产代码中使用它:


更新2我正在链接这个答案,因为它包含了一些关于古怪更新不可靠性的有用信息-。

使用相关子查询。很简单,给你:

SELECT 
somedate, 
(SELECT SUM(somevalue) FROM TestTable t2 WHERE t2.somedate<=t1.somedate) AS running_total
FROM TestTable t1
GROUP BY somedate
ORDER BY somedate
代码可能不完全正确,但我相信这个想法是正确的

如果某个日期出现多次,您只希望在结果集中看到它一次

如果你不介意重复约会,或者 要查看原始值和id,请执行以下操作:

SELECT 
id,
somedate, 
somevalue,
(SELECT SUM(somevalue) FROM TestTable t2 WHERE t2.somedate<=t1.somedate) AS running_total
FROM TestTable t1
ORDER BY somedate
使用连接 另一种变体是使用join。现在,查询可能如下所示:

    SELECT a.id, a.value, SUM(b.Value)FROM   RunTotalTestData a,
    RunTotalTestData b
    WHERE b.id <= a.id
    GROUP BY a.id, a.value 
    ORDER BY a.id;
有关更多信息,请访问此链接

如果您使用的是上面提到的Sql server 2008 R2。那么,这将是最短的方法

Select id
    ,somedate
    ,somevalue,
LAG(runningtotal) OVER (ORDER BY somedate) + somevalue AS runningtotal
From TestTable 
用于获取上一行的值。你可以在谷歌上搜索更多信息


[1] :

尽管最好的方法是使用窗口函数来完成,但也可以使用简单的相关子查询来完成


这里有两种简单的方法来计算运行总数:

方法1:如果您的DBMS支持分析函数,则可以这样编写

SELECT     id
           ,somedate
           ,somevalue
           ,runningtotal = SUM(somevalue) OVER (ORDER BY somedate ASC)
FROM       TestTable
SELECT     T.id
           ,T.somedate
           ,T.somevalue
           ,runningtotal = OA.runningtotal
FROM       TestTable T
           OUTER APPLY (
                           SELECT   runningtotal = SUM(TI.somevalue)
                           FROM     TestTable TI
                           WHERE    TI.somedate <= S.somedate
                       ) OA;
方法2:如果数据库版本/DBMS本身不支持分析功能,则可以使用外部应用

SELECT     id
           ,somedate
           ,somevalue
           ,runningtotal = SUM(somevalue) OVER (ORDER BY somedate ASC)
FROM       TestTable
SELECT     T.id
           ,T.somedate
           ,T.somevalue
           ,runningtotal = OA.runningtotal
FROM       TestTable T
           OUTER APPLY (
                           SELECT   runningtotal = SUM(TI.somevalue)
                           FROM     TestTable TI
                           WHERE    TI.somedate <= S.somedate
                       ) OA;


注意:-如果您必须单独计算不同分区的运行总数,可以按此处发布的方式进行:

在这种情况下,使用OVER with SUM无法给出运行总数。当与SUM一起使用时,OVER子句不接受ORDER BY。您必须使用PARTITION BY,这对运行总计不起作用。谢谢,了解一下为什么它不起作用是非常有用的。araqnid也许你可以编辑你的答案来解释为什么它不是一个选项这对我来说确实有效,因为我需要分区-所以尽管这不是最流行的答案,但它是解决我在SQL中RT问题的最简单的方法。我没有MSSQL 2008,但我认为您可能可以通过选择null进行分区,并绕过分区问题。或者用1个partitionme和该分区进行子选择。另外,在现实生活中做报告时可能需要按分区。这是非常低效的。。。但是,在sql Server中,没有真正干净的方法可以做到这一点,这绝对是低效的-但它可以做到这一点,而且不存在以正确或错误的顺序执行的问题。谢谢,有其他答案很有用,也有助于提高效率critique@Dave我认为这个问题是在试图找到一种有效的方法,交叉连接对于大型集合来说是非常缓慢的。谢谢,有其他答案是有用的,而且有效率也是有用的。谢谢。所以您的代码示例将演示它将按照主键的顺序求和,我想。对于更大的数据集,知道游标是否仍然比连接更有效将是一件有趣的事情。我刚刚测试了CTE@Martin,没有什么能接近更新技巧——游标的读取速度似乎更低。这是一个profiler trace@Martin Denali将有一个非常好的解决方案,这个+1的所有工作都投入到这个答案中-我喜欢更新选项;可以将分区构建到此更新脚本中吗?e、 g如果有一个额外的字段Car color,该脚本能否返回每个Car color分区内的运行总数?最初的Oracle和ANSI-SQL答案现在可以在SQL server 2017中使用。谢谢,非常优雅!将订单添加到您的更新中。。。设置后,您将获得保证。但Order by无法应用于UPDATE语句。。。可以吗?如果您使用的是SQL Server 2012,也可以看到。是的,我认为这相当于Sam Saffron回答中的“测试3”。对于较小的数据集非常有效。缺点是在内部和外部查询中必须使用相同的where子句。由于我的一些日期在几秒钟内完全相同,因此我必须在内部和外部表中添加:row_number over order by txndate和一些复合索引以使其运行。灵活/简单的解决方案。顺便说一句,已测试交叉应用于子查询。。。它稍微快一点。这是非常干净的,可以很好地处理小数据集;比递归CTT更快这对于小数据集也是一个很好的解决方案,但你也必须意识到它意味着某个日期列是唯一的。这个答案值得更多的认可,或者可能它有一些我看不到的缺陷?应该有一个序列号,这样你就可以在ord=ord+1上加入,有时它需要更多的工作。但无论如何,在SQL 2008 R2上,我使用这个解决方案,如果您的数据已经有一个序号,并且您正在寻找基于SQL 2008 R2的简洁的非光标集解决方案,这似乎是完美的。不是每个正在运行的total查询都会有一个连续的序号字段。有时,datetime字段就是您所拥有的,或者记录已从排序中间删除。这可能就是为什么不经常使用它的原因。@Reuben如果您的表足够小,您总是可以将它按顺序数字转储到临时表中,但是,是的,有时此解决方案无法轻松应用于数据库。。。简单是伟大的。有一个性能索引要添加,但这很简单,采用了数据库引擎优化顾问的建议之一;,然后它像一个快照一样运行。你可能应该给出一些关于你在这里做什么的信息,并注意这个特殊方法的任何优点/缺点。我相信只有e
SQL server 2012及以上版本中的xists不是2008使用LAG不会改善SUMsomevalue超过。。。这对我来说似乎干净多了
BEGIN TRAN
CREATE TABLE #Table (_Id INT IDENTITY(1,1) ,id INT ,    somedate VARCHAR(100) , somevalue INT)


INSERT INTO #Table ( id  ,    somedate  , somevalue  )
SELECT 45 , '01/Jan/09', 3 UNION ALL
SELECT 23 , '08/Jan/09', 5 UNION ALL
SELECT 12 , '02/Feb/09', 0 UNION ALL
SELECT 77 , '14/Feb/09', 7 UNION ALL
SELECT 39 , '20/Feb/09', 34 UNION ALL
SELECT 33 , '02/Mar/09', 6 

;WITH CTE ( _Id, id  ,  _somedate  , _somevalue ,_totvalue ) AS
(

 SELECT _Id , id  ,    somedate  , somevalue ,somevalue
 FROM #Table WHERE _id = 1
 UNION ALL
 SELECT #Table._Id , #Table.id  , somedate  , somevalue , somevalue + _totvalue
 FROM #Table,CTE 
 WHERE #Table._id > 1 AND CTE._Id = ( #Table._id-1 )
)

SELECT * FROM CTE

ROLLBACK TRAN
Select id
    ,somedate
    ,somevalue,
LAG(runningtotal) OVER (ORDER BY somedate) + somevalue AS runningtotal
From TestTable 
Select id, someday, somevalue, (select sum(somevalue) 
                                from testtable as t2
                                where t2.id = t1.id
                                and t2.someday <= t1.someday) as runningtotal
from testtable as t1
order by id,someday;
SELECT     id
           ,somedate
           ,somevalue
           ,runningtotal = SUM(somevalue) OVER (ORDER BY somedate ASC)
FROM       TestTable
SELECT     T.id
           ,T.somedate
           ,T.somevalue
           ,runningtotal = OA.runningtotal
FROM       TestTable T
           OUTER APPLY (
                           SELECT   runningtotal = SUM(TI.somevalue)
                           FROM     TestTable TI
                           WHERE    TI.somedate <= S.somedate
                       ) OA;