group by 会压缩行数导致原始字段丢失,而窗口函数如 avg() over(partition by dept) 在保留每行基础上广播组内均值,实现分组不聚合。

为什么不能直接用 GROUP BY 实现“分组但不聚合”
GROUP BY 的本质是把多行压缩成一行,原始行数据必然丢失。比如想算每个部门的平均薪资,同时保留每个人的记录——GROUP BY dept 会强制你写 SELECT dept, AVG(salary),无法再拿到 name 或 id 这类非分组/非聚合字段,否则报错:column "name" must appear in the GROUP BY clause or be used in an aggregate function。
窗口函数不改变行数,只在逻辑分区上“看一眼”并计算,这才是保留原始行的关键。
用 AVG() OVER(PARTITION BY ...) 替代 GROUP BY 求组内均值
这是最常见也最容易上手的替代场景:既要分组统计,又要保留每条记录。
-
AVG(salary) OVER(PARTITION BY dept)会在每个dept分区内计算平均值,并把结果广播到该分区每一行 - 和
GROUP BY dept不同,它不减少行数,也不要求其他字段被聚合或分组 - 注意:
PARTITION BY是必须的,否则就是全表一个分区;ORDER BY在这里可选,除非你要做累计均值(如ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
SELECT name, dept, salary,
AVG(salary) OVER(PARTITION BY dept) AS dept_avg_salary
FROM employees;
ROW_NUMBER() 和 RANK() 处理排序与去重需求
当需要“每个部门薪资最高的人”但又不想丢掉其他字段时,GROUP BY + MAX() 只能给出最大值,无法定位是哪一行。窗口函数配合子查询或 CTE 就能精准锚定。
-
ROW_NUMBER() OVER(PARTITION BY dept ORDER BY salary DESC)为每组内按薪资降序编号,1 就是最高者,且不并列 -
RANK() OVER(...)会并列(相同薪资都得 rank 1),适合“并列第一也要全部保留”的场景 - 别直接在 WHERE 中用窗口函数(如
WHERE rn = 1),因为窗口函数执行晚于 WHERE;必须套一层子查询或用 CTE
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER(PARTITION BY dept ORDER BY salary DESC) AS rn FROM employees ) SELECT name, dept, salary FROM ranked WHERE rn = 1;
容易忽略的性能与语义陷阱
窗口函数不是银弹。PARTITION BY 字段若无索引,大数据量下可能比 GROUP BY 更慢——因为 GROUP BY 可利用哈希或排序提前终止,而窗口函数通常要扫描整个分区。
-
PARTITION BY列最好有索引,尤其当分区粒度细(如按用户 ID 分)且数据量大时 - 误写成
OVER(ORDER BY dept)(没 PARTITION BY)会导致全表排序后累计计算,语义完全不同 - PostgreSQL 和 SQL Server 对空值在
PARTITION BY中的处理一致(归为同一组),但 MySQL 8.0 默认把 NULL 视为独立分区,需留意
真正难的不是写对语法,而是想清楚:你到底要“在哪个维度上局部计算”,以及这个局部是否真的覆盖了业务语义——比如按日期分区算 7 日滚动均值,和按用户分区算个人均值,逻辑完全不可互换。











