avg() over()可直接计算整列平均值并广播至每行,避免子查询性能损耗;支持partition by分组均值、order by累积/移动平均,且默认忽略null,需注意数据类型与数据库兼容性。

直接用 AVG() 窗口函数就能算,不用子查询
想每行减去整列的平均值,最常见错误是写成 SELECT x - AVG(x) FROM t —— 这会报错,因为聚合函数不能和普通列混用。正确做法是把 AVG() 当作窗口函数,加上 OVER()。它不压缩行数,只计算一个广播式均值,每行都能拿到相同结果。
-
AVG(x) OVER()计算整列平均值(忽略 NULL),所有行结果一致 - 如果要按分组算差值,比如“每个部门内薪资与本部门平均薪资的差”,就写
AVG(salary) OVER(PARTITION BY dept) - 注意:
AVG()默认对NULL值静默过滤,不会报错,但如果你的业务里NULL有特殊含义,得先用COALESCE()处理
小心数据类型隐式转换导致精度丢失
比如 salary 是 INT 类型,AVG(salary) OVER() 在 PostgreSQL 和 SQL Server 中仍返回 NUMERIC 或 DECIMAL,但 MySQL 8.0 默认返回 DOUBLE;而 SQLite 根本不支持窗口函数。差值列如果后续要四舍五入或比较,类型不一致可能出问题。
- 显式转类型更稳:
salary - CAST(AVG(salary) OVER() AS DECIMAL(10,2)) - 在 PostgreSQL 中,
AVG(int_col)返回numeric,但参与减法时自动适配;不过做ROUND(..., 2)前最好先CAST,避免浮点误差 - SQLite 用户需升级到 3.25+ 并确认启用窗口函数支持(编译时未禁用)
性能上,OVER() 比关联子查询快得多
有人会写 (SELECT AVG(x) FROM t) 作为标量子查询放在 SELECT 列里,逻辑没错,但数据库通常会对每行重复执行一次该子查询(即使结果一样),尤其在大表上明显变慢。而 AVG() OVER() 只扫描一次源数据,内存中缓存均值后直接广播。
- 在千万级表上,窗口函数耗时通常是子查询的 1/5~1/3
- 如果还加了
PARTITION BY,窗口函数依然只遍历一次,按分区维护累加器;子查询则可能变成 N × 分区数次执行 - 某些旧版 MySQL(ORDER BY,且不可靠
别漏掉 ORDER BY 对累计类窗口的影响(虽然这里不需要)
本例用的是无排序的 OVER(),等价于 OVER(ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)。但如果误加了 ORDER BY,比如 AVG(x) OVER(ORDER BY id),那就变成“从第一行到当前行”的移动平均,不是全局均值了——差值结果完全不对。
- 只要目标是“每行减整列平均值”,
OVER()里一定不要写ORDER BY - 检查执行计划时,留意是否出现
WindowAgg节点(PostgreSQL)或Window Function(SQL Server),而不是嵌套循环子查询 - 如果业务后续要扩展为“前7天滚动差值”,那时才需要
ORDER BY event_time ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
实际用的时候,最常踩的坑其实是忘了不同数据库对 AVG() 空值处理的一致性,以及误加 ORDER BY 导致语义偏移。写完建议用三行小数据手算验证一遍。










