如何在不使用游标的情况下计算SQL中的运行总计?

时间:2009-07-20 14:19:39

标签: sql sql-server

为了简洁,我省略了所有光标设置和临时表中的SELECT。基本上,此代码计算每个事务的所有事务的运行余额。

WHILE @@fetch_status = 0
BEGIN

    set @balance = @balance+@amount

    insert into @tblArTran values ( --from artran table
                @artranid, @trandate, @type, 
                @checkNumber, @refNumber,@custid,
                @amount, @taxAmount, @balance, @postedflag, @modifieddate )


    FETCH NEXT FROM artranCursor into 
            @artranid, @trandate, @type, @checkNumber, @refNumber,
            @amount, @taxAmount,@postedFlag,@custid, @modifieddate

END

受到另一个问题答案的代码的启发,

SELECT @nvcConcatenated = @nvcConcatenated + C.CompanyName + ', '
FROM tblCompany C
WHERE C.CompanyID IN (1,2,3)

我想知道如果得到我的意思,SQL是否能够以与连接字符串相同的方式对数字求和。也就是说,每行创建一个“运行平衡”,而不使用游标。

有可能吗?

11 个答案:

答案 0 :(得分:20)

您可能需要在此处查看对局部变量解决方案的更新:http://geekswithblogs.net/Rhames/archive/2008/10/28/calculating-running-totals-in-sql-server-2005---the-optimal.aspx

DECLARE @SalesTbl TABLE (DayCount smallint, Sales money, RunningTotal money)

DECLARE @RunningTotal money

SET @RunningTotal = 0

INSERT INTO @SalesTbl 
SELECT DayCount, Sales, null
FROM Sales
ORDER BY DayCount

UPDATE @SalesTbl
SET @RunningTotal = RunningTotal = @RunningTotal + Sales
FROM @SalesTbl

SELECT * FROM @SalesTbl

优于所有其他方法,但对保证行顺序有一些疑问。当临时表被索引时似乎工作正常..

  • 嵌套子查询9300毫秒
  • 自我加入6100毫秒
  • 光标400毫秒
  • 更新到本地变量140 ms

答案 1 :(得分:10)

SQL 可以创建运行总计而不使用游标,但它是少数几种情况之一,其中游标实际上比基于集合的解决方案更高效(给定SQL Server中当前可用的运算符)。或者,CLR功能有时可以很好地发挥作用。 Itzik Ben-Gan在SQL Server Magazine上发表了关于运行聚合的优秀系列。该系列节目于上个月结束,但如果您有在线订阅,则可以访问所有文章。

编辑:这是his latest article in the series(SQL CLR)。 鉴于您可以通过购买一个月的在线月票 - 少于6美元来访问整个系列 - 如果您有兴趣从各个角度查看问题,那么值得您花些时间。 Itzik是Microsoft MVP和非常明亮的TSQL编码器。

答案 2 :(得分:8)

OraclePostgreSQL 8.4中,您可以使用窗口函数:

SELECT  SUM(value) OVER (ORDER BY id)
FROM    mytable

MySQL中,您可以将会话变量用于相同目的:

SELECT  @sum := @sum + value
FROM    (
        SELECT  @sum := 0
        ) vars, mytable
ORDER BY
        id

SQL Server中,这是一个罕见的光标是首选解决方案的任务示例。

答案 3 :(得分:4)

计算每条记录的运行总计的示例,但前提是记录的OrderDate位于同一日期。一旦OrderDate用于不同的一天,那么新的运行总计将在新的一天开始并累积:(假设表格结构和数据)

select O.OrderId,
convert(char(10),O.OrderDate,101) as 'Order Date',
O.OrderAmt, 
(select sum(OrderAmt) from Orders 
                      where OrderID <= O.OrderID and 
                           convert(char(10),OrderDate,101)
                         = convert(char(10),O.OrderDate,101))
                               'Running Total' 
from Orders O
order by OrderID

以下是使用示例订单表从查询返回的结果:

OrderId     Order Date OrderAmt   Running Total                            
----------- ---------- ---------- ---------------
1           10/11/2003 10.50      10.50
2           10/11/2003 11.50      22.00
3           10/11/2003 1.25       23.25
4           10/12/2003 100.57     100.57
5           10/12/2003 19.99      120.56
6           10/13/2003 47.14      47.14
7           10/13/2003 10.08      57.22
8           10/13/2003 7.50       64.72
9           10/13/2003 9.50       74.22

请注意,“Running Total”的值从10.50开始,然后变为22.00,最后为OrderID 3的23.25,因为所有这些记录都具有相同的OrderDate(10/11/2003)。但是当显示OrderID 4时,重置运行总计,并且运行总计重新开始。这是因为OrderID 4的OrderDate具有不同的日期,然后OrderID为1,2和3.通过使用相关的子查询再次计算每个唯一日期的此运行总计,尽管需要额外的WHERE条件,确定不同记录的OrderDate需要在同一天。通过使用CONVERT函数将OrderDate截断为MM / DD / YYYY格式来完成此WHERE条件。

答案 4 :(得分:4)

在SQL Server 2012及更高版本中,您可以直接对原始表使用Sum窗口函数:

SELECT
   artranid,
   trandate,
   type,
   checkNumber,
   refNumber,
   custid,
   amount,
   taxAmount,
   Balance = Sum(amount) OVER (ORDER BY trandate ROWS UNBOUNDED PRECEDING),
   postedflag,
   modifieddate
FROM
   dbo.Sales
;

与所有解决方案相比,这将表现得非常好,并且不会出现“古怪更新”中发现的错误。

请注意,您应尽可能使用ROWS版本; RANGE版本可能表现不佳。

答案 5 :(得分:2)

您可以在select子句中包含相关子查询。 (对于非常大的结果集,这将表现不佳)但

   Select <other stuff>,
       (Select Sum(ColumnVal) From Table
        Where OrderColumn <= T.OrderColumn) As RunningTotal
   From Table T
   Order By OrderColumn

答案 6 :(得分:1)

您可以执行运行计数,这是一个示例,请记住,这实际上并不是那么快,因为它必须扫描每一行的表,如果您的表很大,这可以非常耗时且昂贵

create table #Test  (id int, Value decimal(16,4))
insert #Test values(1,100)
insert #Test values(2,100)
insert #Test values(3,100)
insert #Test values(4,200)
insert #Test values(5,200)
insert #Test values(6,200)
insert #Test values(7,200)

select *,(select sum(Value) from  #Test t2 where t2.id <=t1.id) as SumValues
 from #test t1

id  Value       SumValues
1   100.0000    100.0000
2   100.0000    200.0000
3   100.0000    300.0000
4   200.0000    500.0000
5   200.0000    700.0000
6   200.0000    900.0000
7   200.0000    1100.0000

答案 7 :(得分:1)

在SQLTeam上,还有一个关于计算运行总计的article。比较了3种方法,以及一些性能测量:

  • 使用游标
  • 使用subselect(根据SQLMenace的帖子)
  • 使用CROSS JOIN

游标远远超过其他解决方案,但如果你不能使用游标,至少还有另一种选择。

答案 8 :(得分:0)

SELECT @nvcConcatonated位仅返回单个连接值。 (虽然它是按行计算中间值,但您只能检索最终值。)

所以,我认为答案是否定的。如果您想要一个单一的最终总和值,您当然只需使用SUM

我不是说你不能这样做,我只是说你不能用这个'技巧'做到这一点。

答案 9 :(得分:0)

请注意,使用变量来完成此操作(如下所示)可能会在多处理器系统中失败,因为可以在不同的处理器上计算单独的行,并且可能最终使用相同的起始值。我的理解是可以使用查询提示强制它使用单个线程,但我没有那些方便的信息。

更新@SalesTbl   SET @RunningTotal = RunningTotal = @RunningTotal + Sales   来自@SalesTbl

使用其他选项之一(光标,窗口函数或嵌套查询)通常是获得可靠结果的最安全的选择。

答案 10 :(得分:0)

选择TransactionDate,金额,金额+(来自交易x的x.amount,其中x.TransactionDate&lt; Transactions)来自交易的运行总计

,其中     x.TransactionDate&lt;交易 可以是代表除当前记录之外的所有先前记录的任何条件