窗口函数与GROUP BY本质不同:GROUP BY压缩行数输出汇总结果,窗口函数保留原始行数并附加计算列;前者用于统计聚合,后者用于明细增强与有序分析,二者不可混用且执行时机不同。

窗口函数和 GROUP BY 本质不同,不是“结果不一致”,而是“压根不在同一层干活”——强行混用必报错或逻辑错乱。必须分清场景:要保留明细就用窗口函数;要压缩统计就用 GROUP BY;两者都要,就得分两步走。
为什么直接在GROUP BY里写ROW_NUMBER()会报错
SQL 执行顺序是 WHERE → GROUP BY → HAVING → SELECT(窗口函数在此阶段最后执行)。GROUP BY 已把多行压缩成一行,而 ROW_NUMBER() 需要原始多行才能编号,此时上下文已丢失。
- 错误示例:
SELECT dept, COUNT(*), ROW_NUMBER() OVER (ORDER BY COUNT(*)) FROM emp GROUP BY dept→ 报错column "count" must appear in the GROUP BY clause或类似提示 - 根本原因:窗口函数看到的是
GROUP BY后的单行结果集,无法对“组内多行”排序编号 - MySQL 8.0+、PostgreSQL、SQL Server 均如此,不是版本 bug,是语义冲突
想对分组结果再排序/排名,必须用CTE或子查询
先固化聚合结果,再把它当新表开窗。这是唯一可靠、可读、可调试的方式。
- 正确写法(CTE):
WITH dept_summary AS ( SELECT dept, AVG(salary) AS avg_salary FROM emp GROUP BY dept ) SELECT dept, avg_salary, RANK() OVER (ORDER BY avg_salary DESC) AS rank_by_avg FROM dept_summary; - 注意:
dept_summary中的列名必须明确(如avg_salary),不能依赖别名未定义的表达式 - 如果还要过滤(比如只看
avg_salary > 5000的部门),HAVING或WHERE必须放在 CTE 内部,外层窗口函数看不到原始行
想保留明细行又带分组统计值,直接用窗口函数,别碰GROUP BY
这是最常见误用场景:以为要“先分组再加列”,其实只需 OVER(PARTITION BY ...)。
- 错误思路:用
GROUP BY name, dept算部门平均工资,再试图关联回明细 → 多此一举且易出错 - 正确写法:
SELECT name, dept, salary, AVG(salary) OVER (PARTITION BY dept) AS dept_avg→ 每行都带本部门均值,行数不变 - 支持复杂逻辑:比如计算“个人工资比部门平均高多少”:
salary - AVG(salary) OVER (PARTITION BY dept) - 性能友好:无需 JOIN、无中间临时表,数据库优化器通常能高效执行
容易被忽略的细节:NULL 和类型隐式转换影响窗口分区
PARTITION BY 字段若含 NULL,所有 NULL 会被归入同一个窗口 —— 这和 GROUP BY 行为一致,但常被忽视。
- 现象:
COUNT(*) OVER (PARTITION BY category)在category IS NULL的行上返回一个巨大数字,而你预期是“每空类别单独算” - 验证方式:
SELECT category, COUNT(*) FROM t GROUP BY category,看NULL是否被当成一组 - 修复建议:业务上需区分
NULL时,显式处理:PARTITION BY COALESCE(category, '__MISSING__') - 类型问题:若
category是CHAR(10),值'A'实际存为'A ',和VARCHAR的'A'不等价 → 分区分裂,结果分散。统一用TRIM(category)或显式CAST

















