sql server中sum() over(order by...)默认用range帧,遇重复排序值会错误累加;应显式指定rows between unbounded preceding and current row,并确保order by含唯一列(如id或timestamp)以实现正确运行总和。

SQL Server 的 OVER 子句能直接算出运行总和,关键在于正确写 ORDER BY 和避免默认窗口帧导致的“只算当前行”陷阱。
为什么 SUM() OVER (ORDER BY ...) 有时只返回当前行值?
这是最常踩的坑:如果省略 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,SQL Server 默认使用 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。当 ORDER BY 列存在重复值时,RANGE 会把所有相同值的行全纳入当前帧——但更隐蔽的问题是,若没显式指定帧,且排序列无唯一约束,结果可能不符合预期(尤其在分组后叠加排序时)。
- 始终显式写
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,确保逐行累加 - 确保
ORDER BY子句包含足够区分行的列(例如加id或timestamp),避免因重复值导致多行被捆进同一帧 - 不要依赖默认行为——SQL Server 2012+ 的窗口函数默认帧规则和旧版理解有偏差
按日期顺序计算销售金额的运行总和
典型业务场景:查每日销售额,并附上“截至当天的累计销售额”。注意必须用 ROWS 显式限定,且 ORDER BY 要能唯一排序。
SELECT
sale_date,
amount,
SUM(amount) OVER (
ORDER BY sale_date, id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM sales;
-
sale_date是主排序依据,id是辅助排序列,防止同日多笔订单导致顺序不确定 - 如果表里没有自然唯一键,可用
ROW_NUMBER() OVER (ORDER BY sale_date, NEWID())临时补一个,但不推荐用于生产环境 - 别在
OVER里写PARTITION BY,除非你真需要“每类商品各自的运行总和”
和 GROUP BY 混用时的常见错误
想先按月聚合销售总额,再算“各月累计”,不能直接在 GROUP BY 查询里套 SUM() OVER——因为窗口函数在逻辑处理顺序中晚于 GROUP BY,但早于 ORDER BY,所以可以,但要注意聚合粒度。
SELECT
YEAR(sale_date) AS y,
MONTH(sale_date) AS m,
SUM(amount) AS monthly_sum,
SUM(SUM(amount)) OVER (
ORDER BY YEAR(sale_date), MONTH(sale_date)
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total_by_month
FROM sales
GROUP BY YEAR(sale_date), MONTH(sale_date);
- 内层
SUM(amount)是聚合,外层SUM(SUM(...))是窗口累加,语法合法 -
ORDER BY必须和GROUP BY字段一致或为其前缀,否则 SQL Server 报错 - 如果月份字段可能缺失(如 NULL),需用
ISNULL()或CASE处理,否则NULL会排在最前并影响累计逻辑
真正难的是让运行总和在分页、筛选或关联后仍保持语义正确——比如加了 WHERE sale_date >= '2024-01-01' 后,运行总和应从该起点重新累计,而不是跳着算。这时候得把过滤提前到 CTE 或子查询里,再对结果应用 OVER,否则容易漏掉前置数据却误以为是累计起点。










