不熟悉 SQL 运行 完整解决方案的 UPDATE 语句

Unfamiliar SQL UPDATE statement for running total solution

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

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

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

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

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

您的更新语句只是对当前 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 查询计算累计总数,第二个查询更新您的 table。

;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 rows affected
select * from tbl;
GO
FiscalYear | OrderDate           | OrderNumber | SubTotal | RunningTotal
---------: | :------------------ | ----------: | :------- | :-----------
      2011 | 31/05/2011 00:00:00 |           1 | 5000.02  | 5000.02     
      2011 | 31/05/2011 00:00:00 |           2 | 1000.15  | 6000.17     
      2011 | 31/05/2011 00:00:00 |           3 | 700.25   | 6700.42     
      2011 | 31/05/2011 00:00:00 |           4 | 225.02   | 6925.44     
      2011 | 31/05/2011 00:00:00 |           5 | 1258.25  | 8183.69     
      2011 | 31/05/2011 00:00:00 |           6 | 1000.00  | 9183.69     
      2011 | 31/05/2011 00:00:00 |           7 | 695.20   | 9878.89     
      2011 | 31/05/2011 00:00:00 |           8 | 789.25   | 10668.14    
      2011 | 31/05/2011 00:00:00 |           9 | 2125.02  | 12793.16    

db<>fiddle here

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

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

因此,它正在更新整个 table。更新没有 where 子句,因此无论如何都会更新每一行。我个人更喜欢这种为 table 添加别名并对别名使用 UPDATE 的风格,因为当您有复杂的更新时,它可以使查看更改更容易。

内部:

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

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

您的问题的最佳解决方案是可更新的 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;

这没有联接或相关子查询。通常,window 函数将比相关子查询等效函数快得多。可更新的 CTE 是 SQL 服务器的一项非常好的功能,可以让您免于额外的 JOIN