用avg()窗口函数而非group by,是因为窗口函数保留原始行结构并在每行附加组内平均值,而group by会压缩行数;正确写法是avg(score) over (partition by class_id),错误写法如avg(score) over (order by id)会导致累计平均。

为什么用 AVG() 窗口函数而不是 GROUP BY?
因为 GROUP BY 会压缩行数,而窗口函数保留原始行结构,只在每行上附加计算结果。你真正需要的是“每行知道它所在组的平均值”,不是“把数据聚合成一行”。
AVG() OVER (PARTITION BY ...) 的写法要点
核心是 PARTITION BY 定义分组维度,ORDER BY 在这里非必需(除非你要做累积平均)。
- 错误写法:
AVG(score) OVER (ORDER BY id)—— 这会按id做累计平均,不是组内平均 - 正确写法:
AVG(score) OVER (PARTITION BY class_id)—— 每个class_id内算均值,结果广播到该组所有行 - 如果分组依据是多个字段,写成:
PARTITION BY dept, year - 注意:
AVG()自动忽略NULL,但若整组全为NULL,结果也是NULL
常见陷阱:和聚合函数混淆导致报错
典型错误是混用窗口函数和非窗口聚合,比如:SELECT class_id, AVG(score) OVER (PARTITION BY class_id), COUNT(*) FROM students; —— 这会报错,因为 COUNT(*) 没加窗口定义,SQL 引擎不知道你是想全局计数还是组内计数。
- 要组内行数,写成:
COUNT(*) OVER (PARTITION BY class_id) - 要全局行数,写成:
COUNT(*) OVER () - 别漏掉括号:
OVER后必须有(),哪怕里面是空的 - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;SQLite 3.25+ 支持,旧版不支持
性能与 NULL 处理的实际影响
窗口函数不会改变执行计划的本质,但 PARTITION BY 字段如果没有索引,大数据量下可能触发临时排序,拖慢查询。
- 如果
class_id是高频分组字段,建议建索引 -
AVG()对INT列返回DECIMAL,精度可能比预期高(如AVG(1,2,3)返回2.0000),必要时用ROUND(AVG(...), 2) - 空值参与计算时,
AVG(NULL, 100, 90)等价于AVG(100, 90),但AVG(NULL)返回NULL,不是0
窗口函数本身不改行数,但如果你在 WHERE 或 HAVING 里误用了窗口结果(比如 WHERE avg_score > 85),会报错——窗口函数不能直接用于 WHERE,得套一层子查询或 CTE。











