不能在同一个SELECT中混用GROUP BY和窗口函数,必须用CTE或子查询分层处理;窗口函数执行晚于GROUP BY,聚合结果需先固化再开窗;带条件聚合须用CASE WHEN,不可用WHERE;ORDER BY在累计场景中不可省略。

不能直接在同一个 SELECT 中混用 GROUP BY 和窗口函数(如 ROW_NUMBER()、SUM() OVER)——会报错,必须分层处理。
GROUP BY 后想开窗,必须用 CTE 或子查询包一层
窗口函数执行阶段晚于 GROUP BY,所以聚合结果无法直接参与开窗。常见错误是写成 SELECT dept, SUM(salary), ROW_NUMBER() OVER (ORDER BY SUM(salary)) FROM emp GROUP BY dept,MySQL 8.0+ 和 PostgreSQL 都会拒绝:ERROR: column "sum" must appear in the GROUP BY clause or be used in an aggregate function。
正确做法是把聚合结果先固化:
- 用
WITH dept_sum AS (SELECT dept, SUM(salary) AS total FROM emp GROUP BY dept)封装聚合 - 外层再对
dept_sum开窗:SELECT dept, total, ROW_NUMBER() OVER (ORDER BY total DESC) AS rn FROM dept_sum - 如果要取 Top N,最后加
WHERE rn (注意不能在 CTE 内写 <code>WHERE ROW_NUMBER())
COUNT(*) OVER (PARTITION BY x) 不等于 GROUP BY 后的 COUNT(*)
前者统计的是原始行数(x 相同的所有行),后者是分组后每组一行的计数。比如用户表里一个 user_id 出现 5 次,COUNT(*) OVER (PARTITION BY user_id) 在这 5 行上都返回 5;而 GROUP BY user_id + COUNT(*) 只返回一行结果。
这个差异直接影响逻辑判断:
- 想标记“每个用户是否有多条记录”,用
COUNT(*) OVER (PARTITION BY user_id) > 1更直接 - 想统计“有多少用户有多条记录”,就得先去重或用子查询,不能直接对窗口结果再 COUNT
- 若原始数据含 NULL,
PARTITION BY会把所有 NULL 归为同一组(MySQL/PostgreSQL 行为一致)
带条件的窗口聚合必须用 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 (...),不要写ELSE 0,否则 0 会被COUNT统计进去 - ORDER BY 在累计场景中不可省:漏掉会导致窗口按无序行计算,结果不可复现
聚合函数 + OVER 的本质是“广播组内值”,不是二次分组
SUM(salary) OVER (PARTITION BY dept) 的作用,是把部门总工资算出来,然后填到该部门每一行上。它不会减少行数,也不触发新分组逻辑。
这点容易被误用:
- 如果后续还要按
salary > AVG(salary) OVER (PARTITION BY dept)过滤,得用WHERE或HAVING——但注意WHERE不能引用窗口别名,得重写表达式或用外层子查询 - 和
GROUP BY混用时,优先确认目标:要明细行就用窗口;要压缩结果就用分组;两者都要,必须分两层,中间不能跳步 - 性能上,
SUM() OVER ()比SUM()开销大得多,尤其数据量大时,避免在不需要广播的场景硬套窗口

















