📝 聚合函数 vs 窗口函数对比
核心区别:
- 输出行数:聚合函数压缩为1行/组,窗口函数保留原行数
- 分组方式:聚合用GROUP BY,窗口用PARTITION BY
- 排序影响:窗口函数ORDER BY影响计算结果,聚合函数不影响
典型场景:
- 聚合函数:部门汇总报表、HAVING过滤
- 窗口函数:排名计算、累计值、保留明细的同时显示汇总
关键特性:
- 聚合函数可嵌套,窗口函数不可
- 窗口函数只能在SELECT使用,不能用于WHERE
- 执行顺序:WHERE→GROUP BY→HAVING→窗口函数→SELECT
记忆口诀: "聚合压行GROUP BY,窗口留行OVER来, WHERE先走禁窗口,HAVING专管聚合筛"
📊 聚合函数 vs 窗口函数 对比总结表
一、核心区别速查表
| 对比维度 | 聚合函数 | 窗口函数 |
|---|---|---|
| 输出行数 | 多行 → 1 行(数据被压缩) | 多行 → 多行(每行保留) |
| 分组方式 | GROUP BY |
PARTITION BY |
| 排序影响 | ❌ 排序不影响结果 | ✅ ORDER BY 影响累计/排名 |
| 使用位置 | SELECT / HAVING / ORDER BY |
仅 SELECT 后面 |
| 能否嵌套 | ✅ 可以 | ❌ 不可以(但内部可套聚合) |
| 空值处理 | 忽略 NULL(COUNT除外) | 保留 NULL 位置 |
二、执行效果对比表
| 场景 | 聚合函数 | 窗口函数 |
|---|---|---|
| 求部门总薪资 | 一个部门 → 1 行 | 每个员工 → 1 行(都带部门总薪资) |
| 排名 | ❌ 无法实现 | ✅ ROW_NUMBER() |
| 累计求和 | ❌ 无法实现 | ✅ SUM() OVER(ORDER BY) |
| 占比计算 | ❌ 需要子查询 | ✅ SAL / SUM() OVER() |
| 上下行取值 | ❌ 无法实现 | ✅ LAG() / LEAD() |
| 过滤聚合结果 | ✅ HAVING |
❌ 需要子查询 |
三、代码示例对比表
| 需求 | 聚合函数写法 | 窗口函数写法 |
|---|---|---|
| 部门总薪资 | SELECT DEPTNO, SUM(SAL) FROM EMP GROUP BY DEPTNO |
SELECT DEPTNO, SUM(SAL) OVER(PARTITION BY DEPTNO) FROM EMP |
| 部门薪资占比 | ❌ 需要子查询+自连接 | SELECT SAL/SUM(SAL) OVER(PARTITION BY DEPTNO) FROM EMP |
| 薪资排名 | ❌ 无法实现 | SELECT ROW_NUMBER() OVER(ORDER BY SAL DESC) FROM EMP |
| 累计薪资 | ❌ 无法实现 | SELECT SUM(SAL) OVER(ORDER BY HIREDATE) FROM EMP |
| 部门人数≥4 | GROUP BY DEPTNO HAVING COUNT(*)>=4 |
❌ 需要子查询 |
四、使用场景决策表
| 业务需求 | 推荐函数 | 原因 |
|---|---|---|
| 每个部门的汇总报表 | 聚合函数 | 输出简洁,一行一个部门 |
| 每个员工带部门汇总 | 窗口函数 | 保留员工明细,同时显示汇总 |
| TOP N 排名 | 窗口函数 | 聚合函数无法实现 |
| 累计金额/同比环比 | 窗口函数 | 聚合函数无法实现 |
| 过滤分组后的聚合值 | 聚合函数 + HAVING | 窗口函数需要嵌套子查询 |
| 数据切片/分桶 | 窗口函数(NTILE) | 聚合函数无法实现 |
五、语法结构对比表
| 组成部分 | 聚合函数 | 窗口函数 |
|---|---|---|
| 关键字 | GROUP BY |
OVER() |
| 分组 | GROUP BY 字段 |
PARTITION BY 字段 |
| 排序 | ORDER BY(结果排序) |
ORDER BY(组内排序,影响计算) |
| 过滤 | HAVING 过滤组 |
需要子查询 + WHERE |
| 典型函数 | SUM, AVG, COUNT, MAX, MIN | ROW_NUMBER, LAG, LEAD, NTILE, FIRST_VALUE |
六、性能与限制对比表
| 维度 | 聚合函数 | 窗口函数 |
|---|---|---|
| 数据量影响 | 输出行数减少,性能好 | 输出行数不变,内存占用高 |
| 索引利用 | 较好 | 依赖排序,可能产生临时表 |
| 可读性 | 简单直观 | 需要理解 OVER 语法 |
| 数据库支持 | 所有数据库 | 主流数据库(MySQL 8.0+, Oracle, SQL Server, PostgreSQL) |
💡 快速记忆口诀
聚合函数压行数,GROUP BY 来分组
窗口函数留行数,PARTITION 来分区
排名累计用窗口,汇总过滤用聚合
WHERE 提前不能忘,HAVING 专治聚合值
📌 面试常见追问
| 问题 | 答案要点 |
|---|---|
| 窗口函数能写在 WHERE 里吗? | ❌ 不能,执行顺序:WHERE → 窗口函数 → SELECT |
| 聚合函数能实现排名吗? | ❌ 不能,必须用 ROW_NUMBER/RANK 等 |
| 两者能一起用吗? | ✅ 可以,如 SUM(SAL) OVER() + AVG(SAL) |
| 哪个性能更好? | 聚合函数通常更好(数据被压缩),窗口函数需要更多内存 |
📝 SQL 查询语句书写顺序与执行顺序示例
一、完整语法顺序(书写顺序)
sql
SELECT -- 5. 选择要输出的列
列名,
聚合函数,
窗口函数
FROM -- 1. 指定数据来源表
表名
JOIN -- 2. 关联其他表(可选)
另一张表 ON 关联条件
WHERE -- 3. 行级过滤(分组前)
过滤条件
GROUP BY -- 4. 分组
分组字段
HAVING -- 6. 组级过滤(分组后)
聚合条件
ORDER BY -- 7. 排序
排序字段
LIMIT/ROWNUM -- 8. 限制行数
二、实际执行顺序(数据库真正运行的顺序)
| 步骤 | 关键字 | 说明 |
|---|---|---|
| 1 | FROM |
读取表数据 |
| 2 | JOIN |
关联其他表 |
| 3 | WHERE |
过滤行数据(不能有聚合函数) |
| 4 | GROUP BY |
分组 |
| 5 | 聚合函数 | 计算 SUM/AVG/COUNT 等 |
| 6 | HAVING |
过滤分组结果(可以用聚合函数) |
| 7 | 窗口函数 | 计算 ROW_NUMBER/LAG 等 |
| 8 | SELECT |
输出列 |
| 9 | ORDER BY |
排序 |
| 10 | LIMIT |
限制返回行数 |
⚠️ 重点 :窗口函数在 GROUP BY 和 HAVING 之后执行,所以不能在 WHERE 中使用窗口函数
三、完整示例(逐步拆解)
需求:
筛选 1981 年入职且薪资 ≥ 1000 的员工,按部门分组,计算总薪资和排名,要求部门人数 ≥ 3,按总薪资降序取前 2 名
完整 SQL:
sql
SELECT
DEPTNO,
SUM(SAL) AS 总薪资,
COUNT(EMPNO) AS 人数,
ROW_NUMBER() OVER (ORDER BY SUM(SAL) DESC) AS RN
FROM EMP
WHERE TO_CHAR(HIREDATE,'YYYY') = '1981'
AND SAL >= 1000
GROUP BY DEPTNO
HAVING COUNT(EMPNO) >= 3
ORDER BY 总薪资 DESC
LIMIT 2;
执行过程拆解表:
| 步骤 | 操作 | 数据变化 |
|---|---|---|
| 1. FROM | 读取 EMP 表 | 14 条数据 |
| 2. WHERE | 过滤 1981 年入职 & 薪资≥1000 | 9 条数据 |
| 3. GROUP BY | 按 DEPTNO 分组 | 3 个组(10/20/30) |
| 4. 聚合计算 | SUM(SAL), COUNT(EMPNO) | 每组有总薪资和人数 |
| 5. HAVING | 过滤人数 ≥ 3 的组 | 保留 30 号部门(1 个组) |
| 6. 窗口函数 | ROW_NUMBER() 排序 | 给 1 个组加排名 RN=1 |
| 7. SELECT | 输出字段 | DEPTNO,总薪资,人数,RN |
| 8. ORDER BY | 按总薪资降序 | 1 条数据 |
| 9. LIMIT | 取前 2 条 | 1 条数据 |
四、常见错误示例对比表
| ❌ 错误写法 | ✅ 正确写法 | 原因 |
|---|---|---|
WHERE ROW_NUMBER()... = 1 |
使用子查询包裹窗口函数 | 窗口函数在 WHERE 之后执行 |
SELECT SAL + COMM GROUP BY DEPTNO |
SELECT DEPTNO, SUM(SAL+COMM) |
非分组字段不能在 SELECT 中 |
HAVING SAL > 1000 |
WHERE SAL > 1000 |
HAVING 用于聚合条件 |
WHERE SUM(SAL) > 5000 |
HAVING SUM(SAL) > 5000 |
WHERE 不能有聚合函数 |
ORDER BY 别名 在某些数据库报错 |
ORDER BY SUM(SAL) 或重复表达式 |
部分数据库不支持别名排序 |
五、窗口函数特殊顺序说明
sql
-- 窗口函数在 SELECT 阶段计算,但内部有独立顺序
SELECT
ENAME,
SAL,
SUM(SAL) OVER (PARTITION BY DEPTNO ORDER BY HIREDATE) AS 累计薪资
-- ↑ 这个窗口函数内部顺序:
-- 1. PARTITION BY DEPTNO(分组)
-- 2. ORDER BY HIREDATE(排序)
-- 3. 逐行计算累计值
FROM EMP
WHERE DEPTNO = 20; -- 窗口函数只对过滤后的数据计算
六、多表关联时的顺序
sql
SELECT
D.DNAME,
COUNT(E.EMPNO) AS 员工数,
AVG(E.SAL) AS 平均薪资
FROM DEPT D
LEFT JOIN EMP E ON D.DEPTNO = E.DEPTNO -- 1. 先关联
WHERE E.SAL > 1000 OR E.SAL IS NULL -- 2. 再过滤
GROUP BY D.DNAME -- 3. 分组
HAVING COUNT(E.EMPNO) >= 2 -- 4. 过滤组
ORDER BY 平均薪资 DESC; -- 5. 排序
执行流程表:
| 顺序 | 操作 | 结果 |
|---|---|---|
| 1 | FROM DEPT | 4 个部门 |
| 2 | LEFT JOIN EMP | 14 行(保留无员工的部门) |
| 3 | WHERE 过滤 | 过滤薪资>1000 或NULL,约 12 行 |
| 4 | GROUP BY DNAME | 4 个组 |
| 5 | 聚合 COUNT/AVG | 计算每组统计值 |
| 6 | HAVING 过滤 | 保留员工数≥2 的组 |
| 7 | ORDER BY | 按平均薪资降序 |
💡 记忆口诀
FROM 起家 WHERE 挡
GROUP 分组聚合忙
HAVING 再把组来挡
窗口 SELECT 里面藏
ORDER 排队 LIMIT 量
📌 快速自测题
问:下面这条 SQL 会报错吗?为什么?
sql
SELECT
DEPTNO,
SUM(SAL) AS 总薪资,
ROW_NUMBER() OVER (ORDER BY SAL) AS RN
FROM EMP
WHERE RN <= 3
GROUP BY DEPTNO;
答:会报错。
-
WHERE 中使用了 RN 别名(执行顺序问题)
-
WHERE 中使用了窗口函数结果(窗口函数执行晚于 WHERE)
-
GROUP BY 后 SELECT 中 SAL 不是分组字段
正确写法:
sql
SELECT * FROM (
SELECT
DEPTNO,
SUM(SAL) AS 总薪资,
ROW_NUMBER() OVER (ORDER BY SUM(SAL)) AS RN
FROM EMP
GROUP BY DEPTNO
) WHERE RN <= 3;