必须写PARTITION BY,否则窗口函数跨组计算导致结果错误;分组后需在组内按ORDER BY独立排序;ROWS BETWEEN边界自动收缩,重复时间需加唯一键保证稳定性;类型差异需显式转换。

SQL里用AVG()加ROWS做分组内移动平均,必须先写PARTITION BY
不加PARTITION BY,窗口函数会跨组计算,结果完全不对——比如你按用户分组,却算出了所有用户的混合均值。分组后移动平均的核心逻辑是:先切组,再在组内滑动。漏掉PARTITION BY user_id这类字段,ORDER BY create_time就只是全局排序,不是每组独立排序。
常见错误现象:AVG(amount) OVER (ORDER BY create_time ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) 看似合理,但没PARTITION BY,不同用户的记录会混在一起参与滑动计算。
- 必须显式写全分组字段,如
PARTITION BY user_id, product_type - 如果业务上“分组”由多个字段共同决定,少一个就可能合并不该合并的组
- MySQL 8.0+、PostgreSQL、SQL Server 2012+ 支持该语法;SQLite 需 3.25+,旧版不支持
ROWS
ROWS BETWEEN n PRECEDING AND CURRENT ROW 的边界行为要小心
滑动窗口默认包含当前行,但起始位置不足时不会报错,而是自动收缩——比如第一行前面没有两行,那窗口就只含它自己。这通常符合预期,但如果你依赖固定长度窗口(如强制3期),就得额外补数据或过滤。
使用场景:计算最近3笔订单的平均金额,但首两笔订单永远只有1或2个样本参与计算。
-
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW实际窗口长度是动态的:1→2→3→3→3… - 想强制等长?得配合
COUNT(*) OVER (...)判断窗口大小,再用CASE过滤或置空 - 注意
CURRENT ROW是包含当前行的,别误以为是“之前”的截止点
ORDER BY 字段必须有确定性,否则同序记录顺序随机
如果ORDER BY create_time存在重复值(比如批量导入导致多条记录时间戳完全一样),数据库可能任意排列这些行,导致同一查询多次执行结果不一致——移动平均值跟着变。
性能影响:加唯一排序键会略微增加排序开销,但换来结果稳定,值得。
- 推荐写法:
ORDER BY create_time, id或ORDER BY create_time, order_no - 避免仅靠
ORDER BY create_time,尤其在金融、日志类时序数据中 - 某些数据库(如老版本 MySQL)对窗口函数中的
ORDER BY稳定性更敏感,务必验证
MySQL 8.0 和 PostgreSQL 的AVG()窗口结果类型差异
MySQL 的AVG()窗口函数返回DECIMAL,而 PostgreSQL 默认返回NUMERIC,精度和舍入行为可能不同。如果下游是强类型应用(比如 Go 的sql.Scan到float64),小数位数不一致会引发隐式转换问题。
兼容性影响:导出到 BI 工具时,PostgreSQL 可能显示更多小数位,MySQL 截断更早。
- 显式转类型更安全,如
CAST(AVG(amount) OVER (...) AS DECIMAL(10,2)) - 测试时别只看前几行,重点查分组末尾、边界值附近的小数表现
- 如果原始
amount是INT,MySQL 的AVG()仍可能返回带小数的结果,别假设整除
移动平均真正难的不是语法,是确认“组怎么划、序怎么排、空怎么填”。这三个点任何一个模糊,结果就不可复现。尤其是时间字段重复、分组键遗漏、窗口收缩逻辑,最容易上线后才发现偏差。

















