必须显式写 partition by,否则移动平均跨用户错乱;order by 需加唯一键防重复时间戳导致窗口漂移;rows between 2 preceding and current row 自动收缩是标准行为。

必须显式写 PARTITION BY,否则移动平均跨组错乱
不加 PARTITION BY,AVG() OVER () 就是在全表范围内滑动,哪怕你 ORDER BY create_time,不同用户的记录也会混进同一个窗口。比如用户 A 的第 1 笔订单,可能和用户 B 的最后 2 笔一起被算进“最近 3 笔”,结果完全失真。
常见错误写法:AVG(amount) OVER (ORDER BY create_time ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) —— 看似按时间排,实则没分组,所有数据一锅炖。
正确做法是明确业务分组维度:
- 单维度分组(如只按用户):必须加
PARTITION BY user_id - 多维度分组(如用户+商品类型):写全
PARTITION BY user_id, product_type,少一个字段就可能把不该合并的行拉进同一组 - 视图中尤其要警惕:漏掉
PARTITION BY不报错,但指标静默错误,上线后才发现 A 用户的均值里掺了 B 用户的数据
ORDER BY 必须带唯一性,否则同时间戳行顺序随机
仅靠 ORDER BY create_time 是危险的。批量导入、日志打点精度不足等场景下,create_time 大量重复,数据库对这些行的物理顺序不保证——同一查询执行两次,ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 框选的可能是完全不同的三行,移动平均值跟着漂移。
解决方案很简单,加二级排序键:
- 优先用主键:
ORDER BY create_time, id - 无主键时用唯一业务字段:
ORDER BY create_time, order_no - 避免用
ORDER BY create_time, RAND()这类非确定性表达式
注意:RANGE BETWEEN 在重复值场景下更糟,它会把所有同时间戳的行全塞进窗口,彻底破坏“最近 N 条”的语义。
ROWS BETWEEN n PRECEDING AND CURRENT ROW 的边界行为不是 bug,是设计
写 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 算 3 期移动平均,第 1 行只有一条数据参与计算,第 2 行有两条,第 3 行起才满三行——这是 SQL 标准定义的自动收缩行为,不是缺数据或语法错。
如果你需要“前两行强制补零/补原始值”,不能依赖窗口自动处理,得手动干预:
- 保留短窗口结果(推荐):首行就是自身值,符合趋势观察直觉
- 补原始值:
COALESCE(AVG(amount) OVER (...), amount) - 强制从第 3 行起输出:
CASE WHEN ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time)
别试图用 UNBOUNDED PRECEDING 替代——那是累积平均,不是移动平均。
数据库兼容性和性能陷阱要提前踩
ROWS BETWEEN 在 MySQL 8.0+、PostgreSQL、SQL Server 2012+、Snowflake 中完整支持;SQLite 需 3.25+ 且不支持 UNBOUNDED 关键字;旧版 Hive 必须开启 hive.windowing.enabled=true 并确保 ORDER BY 字段唯一。
性能上两个硬伤最常被忽略:
-
ORDER BY字段没索引:窗口函数每次执行都要全表排序,千万级表直接卡死 - 窗口过大(如
365 PRECEDING):每行都要累加数百个值,CPU 和内存压力陡增
优化动作很实在:给 ORDER BY 字段建索引(复合索引注意顺序),并且尽量在 WHERE 子句里先过滤再开窗,别让窗口函数扫全表。










