窗口函数中ORDER BY必须显式指定,否则累计结果不可预测;需用ROWS而非RANGE避免性能问题;去重须在窗口外完成,不可直接用SUM(DISTINCT) OVER。

窗口函数里 ORDER BY 必须写,否则累计值全错
不加 ORDER BY 的 SUM() OVER (PARTITION BY ...) 会默认按“未定义顺序”累加,结果不可预测——不是随机,而是依赖查询计划和数据物理分布,同一语句在不同执行中可能返回不同累计值。
常见错误现象:amount 字段明明有序,但累计列出现跳变或重复值;或者测试数据少时看似正确,上线后数据量增大就崩。
-
ORDER BY必须明确指定排序依据,通常是时间字段(如created_at)或业务序号(如seq_no) - 如果业务上允许多行同序(比如同一秒插入多笔),需额外加唯一键兜底,例如
ORDER BY created_at, id - MySQL 8.0+、PostgreSQL、SQL Server 都要求显式
ORDER BY才支持累计逻辑;SQLite 3.25+ 同样如此
多个维度分组 + 多个方向累计,得用多个 WINDOW 子句
想同时算「按用户分组的金额累计」和「按用户+月份分组的累计」,不能靠嵌套 OVER,得提前定义命名窗口,否则 SQL 可读性爆炸且易出错。
使用场景:报表里既要看个人总趋势,又要看当月内每日进展,两个累计逻辑共存。
- 用
WINDOW w1 AS (PARTITION BY user_id ORDER BY created_at)定义用户级窗口 - 再用
WINDOW w2 AS (PARTITION BY user_id, DATE(created_at) ORDER BY created_at)定义日粒度窗口 - 然后直接写
SUM(amount) OVER w1和SUM(amount) OVER w2,避免重复写冗长表达式 - PostgreSQL 支持该语法;MySQL 8.0.2+、SQL Server 2022 也支持;旧版 MySQL 只能重复写
OVER子句,但务必核对括号和逗号位置
累计值需要去重求和?别在窗口里 DISTINCT
SUM(DISTINCT amount) OVER (...) 是非法语法,所有主流数据库都不支持。想算“每个用户下不重复订单金额的累计”,得换思路。
常见错误现象:直接套用聚合函数思维,写完报错 ERROR 3586 (HY000): Window function 'sum' with DISTINCT is not allowed(MySQL)或类似提示。
- 先用子查询或 CTE 去重:比如按
user_id, order_id去重后再跑窗口函数 - 或者用
ROW_NUMBER() OVER (PARTITION BY user_id, order_id ORDER BY created_at)标记首行,外层过滤rn = 1再累计 - 注意:
DISTINCT在窗口函数外可用,但会破坏分组结构;必须保证去重发生在窗口计算之前
性能敏感时,慎用 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
默认的 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 是安全的,但显式写成 RANGE 模式(尤其配合非唯一排序字段)会导致全扫描,慢到查不出结果。
使用场景:按金额分段累计(如“小于等于当前金额的所有记录之和”),这时容易误用 RANGE。
- 只要排序字段有重复值,
RANGE就会把所有同值行一起纳入当前行窗口,导致逻辑膨胀 - 99% 的业务累计需求用
ROWS足够,且性能稳定;只有极少数分析场景才需RANGE - PostgreSQL 中
RANGE对TIMESTAMP类型默认按微秒对齐,若字段精度低(如只到秒),可能意外合并多行
复杂点在于:窗口定义看着简单,但 ORDER BY 的语义、去重时机、RANGE/ROWS 的行为差异,三者叠加后很容易在数据量上来时才暴露问题。别等上线后被运营拉着改日报逻辑。

















