【问题标题】:Unfamiliar SQL UPDATE statement for running total solution不熟悉的 SQL UPDATE 语句用于运行整体解决方案
【发布时间】:2018-06-08 17:26:48
【问题描述】:

我遇到了以下 SQL UPDATE 语句,它计算表中的运行总计列:

UPDATE N1 SET
    RunningTotal = (SELECT SUM (SubTotal)
                    FROM #Sales X1
                    WHERE
                        N1.FiscalYear = X1.FiscalYear AND
                        X1.OrderNumber <= N1.OrderNumber)
FROM
    #Sales N1

我以前没有见过这种模式,并且由于搜索SQL语句的困难,我一直无法找到解释。具体来说,我想知道上面的语句是如何更新整个表的;循环是如何发生的?

注意:该语句工作正常;在SSMS中看到的前后结果如下:

(我在 Windows 10 x64 上使用 SQL Server 2017 社区版。)

【问题讨论】:

    标签: sql sql-server cumulative-sum


    【解决方案1】:

    这并不是真正的循环,但我会解释一下:

    UPDATE N1 SET
        RunningTotal = ...
    FROM #Sales N1
    

    正因为如此,它正在更新整个表格。更新中没有 where 子句,因此无论如何都会更新每一行。我个人更喜欢这种为表设置别名并针对别名使用 UPDATE 的方式,因为当您有复杂的更新时,它可以更轻松地查看更改。

    内部:

    SELECT SUM (SubTotal)
    FROM #Sales X1
    WHERE N1.FiscalYear = X1.FiscalYear AND
       X1.OrderNumber <= N1.OrderNumber
    

    获取正在处理的订单之前和之前的每年销售金额的总和。它不是在代码中循环;它实际上是在每行或返回的数据运行一个子查询。

    【讨论】:

    • “更新没有 where 子句,所以无论如何都会更新每一行。” 这是我小脑袋里缺少的关键信息。感激不尽。
    【解决方案2】:

    您的更新语句只是对当前 OrderNumber 之前的所有行求和。

    让我告诉你另一种方法:

    create table tbl (FiscalYear int, OrderDate date, OrderNumber int, SubTotal decimal(10,2), RunningTotal decimal(10,2));
    insert into tbl values
    (2011, '20110531', 1, 5000.02, null),
    (2011, '20110531', 2, 1000.15, null),
    (2011, '20110531', 3,  700.25, null),
    (2011, '20110531', 4,  225.02, null),
    (2011, '20110531', 5, 1258.25, null),
    (2011, '20110531', 6, 1000.00, null),
    (2011, '20110531', 7,  695.20, null),
    (2011, '20110531', 8,  789.25, null),
    (2011, '20110531', 9, 2125.02, null);
    GO
    

    CTE 查询计算累计总数,第二个查询更新您的表。

    ;with x as 
    (
        select FiscalYear,
               OrderDate,
               OrderNumber,
               SubTotal,
               sum(SubTotal) over (partition by FiscalYear 
                                   order by FiscalYear, OrderDate, OrderNumber) as CumTotal
        from   tbl
    )
    update t
    set    RunningTotal = CumTotal
    from   tbl t
    join   x
    on     x.Fiscalyear = t.FiscalYear
    and    x.OrderDate = t.OrderDate
    and    x.OrderNumber = t.OrderNumber;
    GO
    
    9行受影响
    select * from tbl;
    GO
    
    财政年度 |订购日期 |订单号 |小计 |运行总计 ---------: | :----------------- | ----------: | :------- | :----------- 2011 | 2011 年 5 月 31 日 00:00:00 | 1 | 5000.02 | 5000.02 2011 | 2011 年 5 月 31 日 00:00:00 | 2 | 1000.15 | 6000.17 2011 | 2011 年 5 月 31 日 00:00:00 | 3 | 700.25 | 6700.42 2011 | 2011 年 5 月 31 日 00:00:00 | 4 | 225.02 | 6925.44 2011 | 2011 年 5 月 31 日 00:00:00 | 5 | 1258.25 | 8183.69 2011 | 2011 年 5 月 31 日 00:00:00 | 6 | 1000.00 | 9183.69 2011 | 2011 年 5 月 31 日 00:00:00 | 7 | 695.20 | 9878.89 2011 | 2011 年 5 月 31 日 00:00:00 | 8 | 789.25 | 10668.14 2011 | 2011 年 5 月 31 日 00:00:00 | 9 | 2125.02 | 12793.16

    db小提琴here

    【讨论】:

    • 我非常非常感谢您的回答。我们可以说您的方法中的显式 JOIN 对应于我的问题中的 SELECT 语句中的隐式连接吗?还是您的方法完全不同?另外,循环是如何在原始语句中发生的?它只是隐式连接的结果吗?再次感谢您。
    • 差不多,先计算累计,再更新表格。
    • 也许您可以一步生成一个临时表。
    【解决方案3】:

    您的问题的最佳解决方案是可更新的 CTE:

    WITH toupdate AS 
          (SELECT S.*,
                  SUM(SubTotal) OVER (PARTITION BY FiscalYear ORDER BY OrderNumber) AS new_RunningTotal
             FROM #Sales S
          )
    UPDATE toupdate
        SET RunningTotal = new_RunningTotal;
    

    这没有连接或相关的子查询。一般来说,窗口函数将比相关子查询等价物快得多。可更新的 CTE 是 SQL Server 的一个非常好的功能,可以让您免于额外的JOIN

    【讨论】:

    • 谢谢。不必要的 JOIN 很昂贵,对吧?
    • @Sabuncu 。 . .一般来说,不必要的连接比没有它们的查询更昂贵。
    • 我试用了您的解决方案。 Sales 表包含 31K 行。我的原始代码在我的系统(Ryzen 5 2400G)上大约需要 45 秒。您在同一张表上的解决方案是即时的,需要零秒! :) 因为我的问题是关于理解原始 SQL 语句,所以我将按原样保留我的答案选择,但如果你愿意比如,我可以发布一个不同的问题,我可以问“你如何优化这个 SQL 语句”并等待你的回答。请告诉我。谢谢
    猜你喜欢
    • 2010-09-14
    • 1970-01-01
    • 1970-01-01
    • 2015-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多