group by 和窗口函数不能同层使用,必须分层:先用 cte 或子查询聚合,再在外层开窗;若需每行显示分组统计值,应直接用窗口函数而非 group by。

GROUP BY 和窗口函数不能同层使用,必须分层
直接在同一个 SELECT 里写 GROUP BY 又用 ROW_NUMBER() OVER 或 SUM() OVER,MySQL 8.0+、PostgreSQL、SQL Server 都会报错,典型错误是:ERROR: column "xxx" must appear in the GROUP BY clause or be used in an aggregate function。这不是语法错,而是执行顺序冲突:GROUP BY 先执行,把原始行压缩成一组一行;窗口函数需要多行上下文才能分区、排序、编号——此时原始行已不存在。
必须用 CTE 或子查询把聚合结果“固化”成一张逻辑新表,再在外层开窗:
- CTE 更清晰:先
WITH agg AS (SELECT dept, SUM(salary) AS total FROM emp GROUP BY dept),再SELECT *, ROW_NUMBER() OVER (ORDER BY total DESC) FROM agg - 子查询也行,但 MySQL 要求显式别名:
SELECT * FROM (SELECT dept, SUM(salary) AS total FROM emp GROUP BY dept) AS t ORDER BY total DESC -
HAVING过滤必须放在 CTE 内部,外层无法对聚合结果再过滤
想保留明细行?别用 GROUP BY,改用窗口聚合
如果目标是“每行都显示,同时附带分组统计值”,比如每位员工的薪资 + 所在部门平均薪资,硬套 GROUP BY 会丢行,还容易引发 ONLY_FULL_GROUP_BY 报错。这时窗口函数才是正解,且无需分层:
- 写法:
SELECT name, dept, salary, AVG(salary) OVER (PARTITION BY dept) AS dept_avg_salary FROM employees - 关键点:
PARTITION BY dept定义分组范围,不压缩行数,原始多少行结果就是多少行 - 注意 NULL:所有
dept IS NULL的行会被归为同一分区,COUNT(*) OVER (PARTITION BY dept)在这些行上返回的是全部 NULL 行总数 - 性能更优:比自连接或相关子查询快,但
PARTITION BY字段若存在数据倾斜(如某部门占 90% 数据),可能拖慢整体执行
WHERE 中引用窗口别名(如 rn)一定会报错
WHERE rn = 1 看起来直觉合理,但数据库会报 Unknown column 'rn'。因为 SQL 执行顺序中,WHERE 在 SELECT(含窗口计算)之前运行,rn 此时根本没生成。
正确做法只有两种:
- 用 CTE:先
WITH ranked AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn FROM orders),再SELECT * FROM ranked WHERE rn = 1 - 用派生表:外层
FROM必须包裹内层查询并加别名,例如SELECT * FROM (SELECT *, ROW_NUMBER() OVER (...) AS rn FROM t) AS tmp WHERE rn = 1 - MySQL 要求子查询必须有别名,否则报
Every derived table must have its own alias
带条件的窗口聚合必须用 CASE WHEN,不能用 WHERE
窗口函数不接受 WHERE 子句。想算“只对已支付订单累计金额”,不能写 SUM(amount) WHERE status = 'paid' OVER ()——语法非法。
必须把条件内联进聚合表达式:
- 金额类:
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) OVER (PARTITION BY user_id ORDER BY created_at),ELSE 0防止 NULL 中断累计 - 计数类:
COUNT(CASE WHEN status = 'paid' THEN 1 END) OVER (PARTITION BY user_id),不要写ELSE 0,否则0会被COUNT统计进去 -
ORDER BY在累计场景中不可省略,否则编号/累计顺序无保证;PARTITION BY缺失则变成全表窗口,不是你想要的分组逻辑
复杂点在于执行顺序不可逆,所有“先要结果、再筛结果”的需求,都得靠 CTE 或子查询把中间态固化下来。窗口函数不是魔法,它依赖完整上下文,而 GROUP BY 是破坏上下文的操作——两者天然互斥,只能分层协作。











