order by是sum() over()计算累计和的强制要求,缺之则每行返回全表总和而非逐行累计;必须显式指定如order by date,并建议加唯一字段防重复值导致顺序不确定。

OVER子句里没写ORDER BY,SUM累计结果全是错的
SQL中SUM() OVER()要算累计和,ORDER BY不是可选——它是强制要求。没有它,窗口默认按无序处理,结果可能每次执行都不一样,甚至每个行都返回全量总和(等价于SUM() OVER()不带ORDER BY时的“未定义行为”)。实际项目里常见错误就是漏掉这句,查半天发现数据对不上。
正确写法必须显式指定排序依据,通常是时间字段或序号列:
SELECT date, amount,
SUM(amount) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumsum
FROM sales;
-
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW是累计和的标准帧定义,不能省略——省略后某些数据库(如PostgreSQL)会默认用这个,但SQL Server和Oracle可能行为不同 - 如果
date有重复值,需加二级排序(如ORDER BY date, id),否则相同日期的行累计顺序不确定 - 别用
RANGE代替ROWS,除非你真需要按值范围聚合;对累计和来说RANGE在重复值下容易多算
想算最近N天的移动总和,得用ROWS BETWEEN配合CURRENT ROW
累计和是“从头到当前”,移动总和是“只看最近N行/天”。关键在调整窗口帧的起始点,而不是改ORDER BY。
例如:计算截至当天、包含当日在内的最近7天销售总额:
SELECT date, amount,
SUM(amount) OVER (
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS moving_7d_sum
FROM sales;
-
6 PRECEDING+CURRENT ROW= 共7行,不是“往前推7天”——这是按物理行数,不是按日期差。若某天缺数据,行数就少于7 - 要严格按日历天数(比如必须包含2024-05-01到2024-05-07),得先用
GENERATE_SERIES或LEFT JOIN补全日期,再套窗口函数 - MySQL 8.0+、PostgreSQL、SQL Server 2012+支持这种
ROWS偏移,但SQLite和旧版SQL Server不支持,会报语法错误
PARTITION BY分组后,累计和只在组内生效
业务常需要“每个用户各自的累计充值额”或“每类产品独立的销量走势”,这时必须加PARTITION BY,且它的优先级高于ORDER BY。
错误写法(把用户ID放ORDER BY里):
-- ❌ 这样只是按user_id排序,不是分组!累计仍跨用户 SUM(amount) OVER (ORDER BY user_id, created_at)
正确写法:
SELECT user_id, created_at, amount,
SUM(amount) OVER (
PARTITION BY user_id
ORDER BY created_at
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS user_cumsum
FROM payments;
-
PARTITION BY必须放在ORDER BY之前,否则语法报错(多数引擎如此) - 分区字段类型要一致,比如别拿
user_id(INT)和user_code(VARCHAR)混用,隐式转换可能导致分区失效 - 如果分区键有NULL,不同数据库处理不同:PostgreSQL把NULL当独立一组,MySQL可能忽略或报错,建议提前
WHERE user_id IS NOT NULL
性能差?检查ORDER BY字段有没有索引
SUM() OVER(ORDER BY ...)本质要对每行做前缀扫描,没索引时会触发全表排序,百万级表可能秒变分钟级。
- 确保
ORDER BY里的字段(如date、created_at)上有索引,复合索引要匹配顺序:CREATE INDEX idx_sales_date ON sales(date); - 如果同时
PARTITION BY和ORDER BY,索引应为(partition_col, order_col),例如CREATE INDEX idx_payments_user_date ON payments(user_id, created_at); - 执行计划里看到
WindowAgg节点耗时高,基本就是缺索引;但注意,索引无法加速ROWS BETWEEN N PRECEDING这类带偏移的移动窗口,此时只能靠减少数据量或物化中间结果
真正麻烦的是跨多个时间粒度(比如既要按天累计,又要按月滚动)或者需要实时更新的场景——这时候窗口函数只是起点,后续往往得结合物化视图或应用层缓存,不然每次查都在算。










