窗口函数与GROUP BY本质不同:前者保留原始行数并追加计算列,后者压缩行数生成聚合结果;二者不可互替,需依需求选择。

窗口函数和GROUP BY根本不是替代关系
窗口函数不能替代 GROUP BY,因为二者作用层级不同:前者追加计算列,后者压缩行数。想把 100 行员工数据按部门汇总成 5 行统计结果?必须用 GROUP BY。想保留全部 100 行,同时在每行标出“本部门平均薪资”?只能靠 AVG(salary) OVER (PARTITION BY dept)。
报错 ERROR 1055 时别硬套 GROUP BY
常见错误写法:SELECT name, dept, AVG(salary) FROM employees GROUP BY dept —— MySQL 直接报 ERROR 1055,因为 name 既没出现在 GROUP BY 里,也没被聚合包裹。
- 硬加
MAX(name)可能语法通过,但语义错乱:返回的姓名可能不属于该组平均薪资对应的人 - 若真要查明细+分组统计值,改用窗口函数:
SELECT name, dept, salary, AVG(salary) OVER (PARTITION BY dept) AS dept_avg - 若只要汇总报表,就老老实实只选分组字段和聚合结果:
SELECT dept, AVG(salary) FROM employees GROUP BY dept
PARTITION BY 不是可选项,漏写就全表计算
PARTITION BY 是窗口函数的分组逻辑,相当于 GROUP BY 的语义,但不压缩行。漏写它,SUM()、AVG() 就变成对整张表计算,所有行都显示同一个数——这不是 bug,是明确行为。
-
COUNT(*) OVER (PARTITION BY user_id)→ 每个用户有多少条记录,原样保留在每行 -
COUNT(*) OVER ()→ 全表总行数,每行都填这个数字 -
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)→ 必须带ORDER BY才有意义;不加会触发 PostgreSQL 报错,MySQL 则行为不可靠
WHERE 里不能直接用窗口函数结果
比如想取每个用户的最新订单,写 WHERE ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) = 1 一定报错——窗口函数执行阶段晚于 WHERE。
- 正确做法是嵌套一层:
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 - 同理,
HAVING也不能用窗口函数,它只作用于GROUP BY后的聚合结果 - 排序和帧定义(如
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)会影响累计类函数行为,不写ORDER BY时SUM() OVER (PARTITION BY x)返回的是整组总和,而非逐行累加
窗口函数的关键约束不在语法上,而在执行顺序和语义边界:它不改变原始行结构,也不参与过滤或分组决策。真正容易被忽略的,是它的执行时机——比 WHERE、GROUP BY、HAVING 都晚,比 SELECT 列计算早。这点不厘清,再漂亮的写法也会在运行时报错或返回意外结果。

















