Oracle 聚合函数 vs 窗口函数 对比总结(书写顺序与执行顺序示例)

📝 聚合函数 vs 窗口函数对比

核心区别:

  1. 输出行数:聚合函数压缩为1行/组,窗口函数保留原行数
  2. 分组方式:聚合用GROUP BY,窗口用PARTITION BY
  3. 排序影响:窗口函数ORDER BY影响计算结果,聚合函数不影响

典型场景:

  • 聚合函数:部门汇总报表、HAVING过滤
  • 窗口函数:排名计算、累计值、保留明细的同时显示汇总

关键特性:

  1. 聚合函数可嵌套,窗口函数不可
  2. 窗口函数只能在SELECT使用,不能用于WHERE
  3. 执行顺序: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;

答:会报错。

  1. WHERE 中使用了 RN 别名(执行顺序问题)

  2. WHERE 中使用了窗口函数结果(窗口函数执行晚于 WHERE)

  3. 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;
相关推荐
weixin_381288182 小时前
HTML5中Noscript标签在脚本禁用环境下的补救
jvm·数据库·python
S1998_1997111609•X2 小时前
k:file/~*…/code/*iOS/an/app-/log in/ext./-system.API/-(NFV)=sdk.
数据库·网络协议·百度·微信·ssh
其实防守也摸鱼2 小时前
网络安全与数据库运维核心知识点总结(附习题)
运维·网络·数据库·笔记·安全·web安全
2401_837163892 小时前
PHP怎么写API接口_RESTful API基础写法介绍【介绍】
jvm·数据库·python
qq_413502022 小时前
PHP跨平台部署AI应用_Docker容器化方案【教程】
jvm·数据库·python
倔强的石头1062 小时前
kingbase备份与恢复实战(五)—— PITR时间点恢复:恢复到误操作前一分钟(归档WAL)
数据库·备份与恢复
2401_832365522 小时前
HTML怎么区分正文与广告_HTML aside与广告位语义【技巧】
jvm·数据库·python
jnrjian2 小时前
SQL太长导致 library cache lock 长期持有 造成系统hang住
数据库·sql
南棱笑笑生2 小时前
20260427给万象奥科的开发板HD-RK3576-PI适配瑞芯微原厂的Android14时增加ll命令
数据库·rockchip