【问题标题】:How do I calculate a running total in SQL without using a cursor?如何在不使用游标的情况下计算 SQL 中的运行总计?
【发布时间】:2010-11-12 08:06:31
【问题描述】:

为简洁起见,我从临时表中省略了所有游标设置和 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 是否能够以与连接字符串相同的方式对数字求和。也就是说,在不使用游标的情况下为每行创建一个“运行余额”。

有可能吗?

【问题讨论】:

标签: sql sql-server


【解决方案1】:

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

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

我不是说你做不到,我只是说你不能用这个“技巧”来做。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 与这个问题有点不相干,但要补充一点,Solid Quality Mentors 提供了一些优秀的书籍,它是 Itzik Ben-Gan 创立的。它们是迄今为止我在 SQL Server 上购买的最好的书籍,提供了我在其他地方找不到的信息。 solidq.com/na/OurBooks.aspx
    • 我没有勾选这个答案,因为我们仍然得到了很多关于这个问题的好答案,我不想阻止它
    【解决方案3】:

    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 中,这是一个罕见的任务示例,其中光标是首选解决方案。

    【讨论】:

    • 如果您可以访问SQL Server 2012,那么您可以使用相同的窗口函数来计算运行总计。
    【解决方案4】:

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

    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
    

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

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

        • 使用游标
        • 使用子选择(根据 SQLMenace 的帖子)
        • 使用交叉连接

        游标的性能远远优于其他解决方案,但如果您不能使用游标,至少还有一个替代方案。

        【讨论】:

          【解决方案7】:

          您可能想在此处查看局部变量解决方案的更新: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 毫秒

          【讨论】:

          • 哇,这篇文章真棒!我不知道这是可能的。感谢发帖。
          • Aaron Bertrand 很好地概述了这个 (stackoverflow.com/a/11313533/26167) 答案中的各种方法,包括关于这种方法的更多注意事项。
          【解决方案8】:

          计算每条记录的运行总计的示例,但前提是记录的 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 相同(2003 年 11 月 10 日)。但是当显示 OrderID 4 时,运行总计被重置,并且运行总计重新开始。这是因为 OrderID 4 的 OrderDate 具有不同的日期,然后是 OrderID 1、2 和 3。计算每个唯一日期的运行总计再次通过使用相关子查询完成,尽管需要额外的 WHERE 条件,即确定不同记录上的 OrderDate 需要是同一天。这个 WHERE 条件是通过使用 CONVERT 函数将 OrderDate 截断为 MM/DD/YYYY 格式来实现的。

          【讨论】:

            【解决方案9】:

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

            更新@SalesTbl SET @RunningTotal = RunningTotal = @RunningTotal + 销售额 来自@SalesTbl

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

            【讨论】:

              【解决方案10】:

              在 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 版本的性能可能不太好。

              【讨论】:

                【解决方案11】:

                select TransactionDate, amount, amount + (sum x.amount from transactions x where x.TransactionDate

                在哪里 x.TransactionDate

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-05-17
                  • 2017-05-16
                  • 1970-01-01
                  • 2023-03-23
                  • 2011-03-15
                  • 1970-01-01
                  相关资源
                  最近更新 更多