窗口函数不改变行数,group by必须压缩行:前者在每行附加计算值(如avg(salary) over(partition by dept)),输出行数=输入行数;后者将多行压缩为一行(如select dept, avg(salary) from emp group by dept),丢失明细字段。

窗口函数不改变行数,GROUP BY必须压缩行
这是最根本的差异:执行 AVG(salary) OVER (PARTITION BY dept),结果行数和原始表完全一致;而 SELECT dept, AVG(salary) FROM emp GROUP BY dept 会把每个部门所有员工压缩成一行。如果你需要在每条员工记录旁显示“本部门平均工资”,用 GROUP BY 就做不到——它直接消灭了员工明细。
SELECT 中混用非聚合字段时,GROUP BY 报错,窗口函数合法
常见错误写法:SELECT name, dept, AVG(salary) FROM emp GROUP BY dept —— 这在标准 SQL 下必然报错,因为 name 既没出现在 GROUP BY 列表里,也没被聚合。但换成窗口函数就自然成立:SELECT name, dept, AVG(salary) OVER (PARTITION BY dept),name 和 dept 都原样保留,每行都带一个计算值。
关键点在于:
-
GROUP BY后的SELECT只能出现分组键或聚合表达式 -
OVER运行在完整行集上,所有原始列可自由引用 - 想同时看“张三的工资”和“销售部平均工资”,只能靠窗口函数
PARTITION BY 不等于 GROUP BY,ORDER BY + ROWS 才定义真正计算范围
PARTITION BY dept 只是划出“和谁比”的边界,不决定“从哪算到哪”。比如 SUM(sales) OVER (PARTITION BY region ORDER BY date) 在某些数据库(如旧版 MySQL)默认按 RANGE 语义,遇到同一天多笔订单,会把当天全部销量一次性加进当前行的累计值;而显式写 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 才保证严格按物理顺序逐行累加。
容易踩的坑:
- 漏写
ORDER BY却依赖累计逻辑 → 结果无序、不可复现 - 用
RANGE处理高基数时间字段(如毫秒级 timestamp)→ 性能陡降,因需对重复值做等值合并 - 误以为
PARTITION BY自动排序 → 实际不排序,ROW_NUMBER()等函数会报错
多个不同 PARTITION BY 的窗口函数可能触发多次扫描
写一条查询:SELECT user_id, SUM(amount) OVER (PARTITION BY user_id), AVG(amount) OVER (PARTITION BY product_type), COUNT(*) OVER (),PostgreSQL 或 SQL Server 很可能为三个分区各自执行一次分组排序,而不是复用中间结果。MySQL 8.0+ 对单表多窗口有合并优化,但一旦涉及 JOIN,优化就基本失效。
性能提示:
- 全局统计(如总金额)优先用
SUM(x) OVER (),比子查询(SELECT SUM(x) FROM t)更快且免关联 - 避免在
ORDER BY中对高基数列(如 UUID、毫秒时间戳)开窗 → 排序开销爆炸 -
PARTITION BY列存在大量 NULL 时,各数据库处理不一致:PostgreSQL 把 NULL 当独立分区,MySQL 行为依赖版本,别依赖它做逻辑分支











