窗口函数与group by的本质区别在于输出行数:窗口函数保留原表所有行,每行附加计算值,输出行数=输入行数;group by压缩行数,输出行数≤输入行数,丢失明细。

窗口函数和 GROUP BY 的本质区别在哪?
窗口函数不会压缩行数,GROUP BY 会。这是保留原始行细节的前提。如果你用了 GROUP BY 却还想看到每条记录的原始字段,说明你误用了聚合逻辑——该用窗口函数的地方用了分组。
-
AVG(salary) OVER (PARTITION BY dept):每行都返回所在部门的平均工资,原始员工姓名、ID、入职日期全在 -
AVG(salary) FROM emp GROUP BY dept:只返回一行/部门,其他字段必须进GROUP BY或套ANY_VALUE()(MySQL)或报错(PostgreSQL)
常见错误现象:column "name" must appear in the GROUP BY clause or be used in an aggregate function —— 这不是语法问题,是逻辑选错。
哪些聚合函数能直接用于窗口?
绝大多数标准聚合函数都支持窗口语法,但行为和普通用法不同:它们在窗口内计算,不丢行。
- 支持的典型函数:
SUM()、AVG()、COUNT()、MAX()、MIN()、STDDEV() - 注意
COUNT(*)和COUNT(col)区别:前者统计非空行数(含 NULL),后者跳过NULL值 -
STRING_AGG(col, ',') OVER (PARTITION BY id)(PostgreSQL)或GROUP_CONCAT(col) OVER (PARTITION BY id)(MySQL 8.0+)可拼接字符串,仍保留每行
不支持的函数:DISTINCT 不能单独作窗口函数;LIMIT/OFFSET 不能出现在窗口定义中;ORDER BY 在 OVER() 里是排序依据,不是结果排序。
ORDER BY 在 OVER() 里到底影响什么?
它只影响窗口帧(frame)的计算顺序,不影响最终结果集顺序。没写 ORDER BY,默认帧是 RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING(整窗口);写了,则可能触发默认帧变为 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(累计计算)。
- 想算“部门内按薪资升序的累计人数”:用
COUNT(*) OVER (PARTITION BY dept ORDER BY salary) - 想算“部门平均薪资(不管排序)”:用
AVG(salary) OVER (PARTITION BY dept),加不加ORDER BY结果一样 - 错误用法:
SELECT *, AVG(salary) OVER (ORDER BY hire_date) FROM emp—— 这会跨部门计算滑动平均,通常不是本意
性能提示:带 ORDER BY 的窗口函数一般比不带的开销大,尤其数据量大时;PostgreSQL 中还可能触发强制排序。
为什么结果看起来“重复”或“不对”?
根本原因常是 PARTITION BY 划分粒度太粗或太细。
- 太粗:比如只按
YEAR(hire_date)分区,但你想看“每月每个岗位”的平均薪资 → 应写PARTITION BY YEAR(hire_date), job_title - 太细:比如
PARTITION BY id,那每个窗口就一行,AVG()就等于原值,毫无意义 - NULL 值陷阱:
PARTITION BY dept时,所有dept IS NULL的行会被分到同一组 —— 如果业务上它们不属于同一逻辑组,得先COALESCE(dept, 'unknown')或过滤
另一个隐形坑:ROWS vs RANGE 帧类型。默认 RANGE 对 ORDER BY 相同值会把它们全纳入当前帧;ROWS 只认物理行序。时间字段用 RANGE 更自然,主键用 ROWS 更可控。
窗口函数本身不难,难的是想清楚“我要对哪几行做聚合、这些行怎么界定、顺序是否影响结果”。写之前先手写两行样例,模拟窗口范围,比直接跑 SQL 更快定位问题。











