窗口函数(OVER)可在SELECT中实现分组计算而不改变行数,核心包括:
1)排名函数(ROW_NUMBER/RANK/DENSE_RANK)用于TOPN查询;
2)LAG/LEAD获取相邻行数据;
3)FIRST_VALUE取组内首条记录;
4)NTILE数据分桶;
5)聚合开窗(SUM/AVG)实现累计计算。
与聚合函数不同,窗口函数保留原行数,通过PARTITION BY分组、ORDER BY排序。
典型应用包括部门薪资排名、同环比分析、累计求和等,需注意结果需子查询过滤,且必须明确排序规则。
(课堂笔记)窗口函数/开窗函数/分析函数
补充:窗口函数或开窗函数,为什么会叫窗口或开窗,怎么理解的
以下是根据你提供的课堂内容,整理出的**《窗口函数(开窗函数/分析函数)完整复习笔记》**,结构清晰、便于学生复习和记忆:
📌 一、窗口函数概述
基本语法:
sql
函数() OVER (PARTITION BY 分组字段 ORDER BY 排序字段)
核心特点:
| 特点 | 说明 |
|---|---|
✅ 只能写在 SELECT 后面 |
不能用在 WHERE/HAVING 中 |
| ✅ 不改变行数 | 每行返回一个值,不会合并多行 |
| ✅ 可分组+排序 | 通过 PARTITION BY 分组,ORDER BY 排序 |
PARTITION 可以省略
PARTITION by 1 order by 1 等于默认值
📌 窗口函数 vs 聚合函数:
聚合函数:多行 → 1 行
窗口函数:多行 → 多行(每行带聚合结果)
📌 二、排名函数(取 TOP N)
1. 三种排名函数对比
| 函数 | 特点 | 并列场景示例 |
|---|---|---|
ROW_NUMBER() |
连续不重复 | 1,2,3,4 |
RANK() |
重复有间隔 | 1,1,3,4 |
DENSE_RANK() |
重复无间隔 | 1,1,2,3 |
RANK 排名 DENSE 稠密,密集
RANK() 并列跳号 1,2,2,4
DENSE_RANK() 并列不跳 1,2,2,3
2. 基础语法
sql
ROW_NUMBER() OVER (PARTITION BY 分组字段 ORDER BY 排序字段)
3. 实战示例
✅ 每个部门按薪资排名
sql
SELECT
ROW_NUMBER() OVER (PARTITION BY DEPTNO ORDER BY SAL DESC) AS RN,
E.*
FROM EMP E;
注意:E.*可以用,直接使用*不行,因为*包括所有,后面不能再加其他字段。
✅ 每个部门薪资前三名
sql
SELECT * FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY DEPTNO ORDER BY SAL DESC) AS RN,
E.*
FROM EMP E
) WHERE RN <= 3;
✅ 公司薪资第 6 名
sql
SELECT * FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY SAL DESC) AS RN,
E.*
FROM EMP E
) WHERE RN = 6;
✅ 每年入职员工月薪前三名
sql
SELECT * FROM (
SELECT
ROW_NUMBER() OVER (
PARTITION BY TO_CHAR(HIREDATE,'YYYY')
ORDER BY SAL + NVL(COMM,0) DESC
) AS RN,
E.*
FROM EMP E
) WHERE RN <= 3;
📌 三、LAG / LEAD(取上下行,计算同环比)
1. 语法
sql
LAG(字段, 偏移量, 缺省值) OVER (PARTITION BY 分组 ORDER BY 排序)
LEAD(字段, 偏移量, 缺省值) OVER (PARTITION BY 分组 ORDER BY 排序)
| 函数 | 说明 |
|---|---|
LAG |
取上一行(往上偏移) |
LEAD |
取下一行(往下偏移) |
✅ 偏移量默认 = 1,缺省值默认 = NULL
空值不会触发缺省值,会保持为空。
函数 单词 中文含义 词性 LAG lag 滞后、延迟、落后 动词/名词 LEAD lead 领先、超前、引导 动词/名词
2. 实战示例
✅ 与上一个入职员工薪资差
sql
SELECT
E.*,
ABS(SAL - LAG(SAL, 1, 8888) OVER (ORDER BY HIREDATE)) AS 薪资差
FROM EMP E;
✅ 每个部门中,与下一个入职员工薪资差
sql
SELECT
E.*,
LEAD(SAL) OVER (PARTITION BY DEPTNO ORDER BY HIREDATE) AS NEXT_SAL
FROM EMP E;
📌 四、FIRST_VALUE(取每组第一个值)
1. 语法
sql
FIRST_VALUE(目标字段) OVER (PARTITION BY 分组 ORDER BY 排序)
✅ 取每组排序后的第一条数据的指定字段值
2. 实战示例
✅ 每个部门薪资最高的员工姓名
sql
SELECT DISTINCT
FIRST_VALUE(ENAME) OVER (PARTITION BY DEPTNO ORDER BY SAL DESC) AS 最高薪资员工,
DEPTNO
FROM EMP;
💡 配合
ROW_NUMBER方式(更灵活):
sql
SELECT DEPTNO, ENAME FROM (
SELECT
DEPTNO, ENAME,
ROW_NUMBER() OVER (PARTITION BY DEPTNO ORDER BY SAL DESC) AS RN
FROM EMP
) WHERE RN = 1;
📌 五、NTILE(数据切片 / 分桶)
1. 语法
sql
NTILE(桶数) OVER (PARTITION BY 分组 ORDER BY 排序)
✅ 将数据分成 N 份,每行标记所属桶号(1 ~ N)
NTILE 整体读作:/ˈen.taɪl/(恩-太欧)
(重音在第一个音节 "en" 上)
在专业交流中(如讨论数据分桶),通常直接读 "N-tile"(强调 N 的数字,如 "4-tile" 表示四分位)即可,对方更容易理解。
2. 实战示例
✅ 每个部门薪资分成 3 档(高/中/低),输出高薪员工
sql
sql
SELECT * FROM (
SELECT
NTILE(3) OVER (PARTITION BY DEPTNO ORDER BY SAL DESC) AS NT,
E.*
FROM EMP E
) WHERE NT = 1;
切片规则
余数分配规律图
sql
余数决定前几个桶多1行:
余数=0: ████ ████ ████ ████ (全部平均)
余数=1: █████ ████ ████ ████ (桶1多1)
余数=2: █████ █████ ████ ████ (桶1,桶2多1)
余数=3: █████ █████ █████ ████ (桶1,2,3多1)
图例:████ = 基数行 █ = 多出的1行
快速判断流程图
第一步:计算桶大小
第二步:数据排序
第三步:按桶大小,依次填充,一个填满了才开始下一个桶填充。
sql
开始
│
▼
┌─────────────────┐
│ 总行数 ÷ 桶数 │
└────────┬────────┘
│
▼
┌─────────────────┐ ┌─────────────────┐
│ 有 余数 吗? │────NO─→│ 所有桶平均分配 │
└────────┬────────┘ └─────────────────┘
│YES
▼
┌─────────────────┐
│ 前(余数)个桶 │
│ 各多分1行 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 从第1行开始 │
│ 按桶容量依次填充 │
└─────────────────┘
正确理解:
✅ 是"尽可能平均",不是"绝对平均"
✅ 多余的行给编号小的桶
✅ 和排序后的位置强相关
记忆口诀
NTILE 分桶按顺序,先排好队再分配
除不尽的余数行,前几个桶多一个位
📌 六、聚合开窗函数(累计 / 占比)
1. 语法
sql
SUM() OVER (PARTITION BY 分组 ORDER BY 排序)
AVG() OVER (PARTITION BY 分组 ORDER BY 排序)
✅ 带
ORDER BY:累计值(逐行累加)✅ 不带
ORDER BY:全组聚合值(每行相同)
关联阅读推荐
2. 实战示例
✅ 公司累计人力成本(随时间推移)
sql
SELECT
E.*,
SUM(SAL) OVER (ORDER BY HIREDATE) AS 累计薪资
FROM EMP E;
✅ 每个部门累计人力成本
sql
SELECT
E.*,
SUM(SAL) OVER (PARTITION BY DEPTNO ORDER BY HIREDATE) AS 部门累计薪资
FROM EMP E;
✅ 员工薪资占部门总薪资比例
sql
SELECT
E.*,
SAL / SUM(SAL) OVER (PARTITION BY DEPTNO) AS 部门薪资占比
FROM EMP E;
✅ 员工薪资占公司总成本比例
sql
SELECT
E.*,
SAL / SUM(SAL) OVER () AS 公司薪资占比
FROM EMP E;
✅ 每个部门累计平均薪资
sql
SELECT
E.*,
AVG(SAL) OVER (PARTITION BY DEPTNO ORDER BY HIREDATE) AS 累计平均薪资
FROM EMP E;
📌 七、窗口范围控制(了解)
最近几行(当前行的之前或之后几行)
sql
SUM(SAL) OVER (
PARTITION BY DEPTNO
ORDER BY HIREDATE
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
)
| 关键字 | 含义 |
|---|---|
PRECEDING |
之前 |
FOLLOWING |
之后 |
CURRENT ROW |
当前行 |
💡 一般笔试/面试较少深入,了解即可。
📌 八、常见面试题
Q1:用过哪些窗口函数?
| 场景 | 函数 |
|---|---|
| TOP N 排名 | ROW_NUMBER / RANK / DENSE_RANK |
| 同环比 | LAG / LEAD |
| 数据切片 | NTILE |
| 累计金额/占比 | SUM / AVG OVER |
Q2:窗口函数和聚合函数的区别?
| 对比点 | 聚合函数 | 窗口函数 |
|---|---|---|
| 输出行数 | 多行 → 1 行 | 多行 → 多行 |
| 分组方式 | GROUP BY |
PARTITION BY |
| 使用位置 | SELECT / HAVING | 仅 SELECT |
| 能否累计 | ❌ | ✅(带 ORDER BY) |
📌 九、课堂练习(自测用)
练习1:每个办公地点薪资前三名
sql
SELECT LOC, ENAME, SAL FROM (
SELECT
LOC, ENAME, SAL,
ROW_NUMBER() OVER (PARTITION BY LOC ORDER BY SAL DESC) AS RN
FROM EMP E
JOIN DEPT D ON E.DEPTNO = D.DEPTNO
) WHERE RN <= 3;
练习2:每个岗位上一个入职员工的薪资
sql
SELECT
JOB, HIREDATE, SAL,
LAG(SAL) OVER (PARTITION BY JOB ORDER BY HIREDATE) AS 上一个薪资
FROM EMP;
练习3:每年薪资最高的员工姓名
sql
SELECT DISTINCT
TO_CHAR(HIREDATE,'YYYY') AS YEAR,
FIRST_VALUE(ENAME) OVER (
PARTITION BY TO_CHAR(HIREDATE,'YYYY')
ORDER BY SAL DESC
) AS ENAME
FROM EMP;
📌 十、易错提醒
| ❌ 错误写法 | ✅ 正确理解 |
|---|---|
WHERE RN <= 3 直接使用别名 |
窗口函数的结果需要子查询 |
PARTITION BY 后加 WHERE |
分区是分组,不是过滤 |
| 忘记 ORDER BY 导致排名不准确 | 排名/累计必须明确排序规则 |
| 把 LAG 当 LEAD 用 | LAG = 向上取,LEAD = 向下取 |
📊 SQL 窗口函数速查表(一页全)
🔧 基础语法
sql
函数() OVER (PARTITION BY 分组字段 ORDER BY 排序字段)
📌 排名函数(TOP N)
| 函数 | 效果 | 并列示例 |
|---|---|---|
ROW_NUMBER() |
连续不重复 | 1,2,3,4 |
RANK() |
有间隔 | 1,1,3,4 |
DENSE_RANK() |
无间隔 | 1,1,2,3 |
sql
-- 每个部门薪资前三名
SELECT * FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY DEPTNO ORDER BY SAL DESC) AS RN, E.*
FROM EMP E
) WHERE RN <= 3;
📌 LAG / LEAD(上下行)
| 函数 | 说明 |
|---|---|
LAG(字段,偏移,缺省) |
取上一行 |
LEAD(字段,偏移,缺省) |
取下一行 |
sql
-- 与上一个入职员工薪资差
SELECT SAL - LAG(SAL) OVER (ORDER BY HIREDATE) FROM EMP;
📌 FIRST_VALUE(每组首个)
sql
-- 每个部门薪资最高的员工
SELECT DISTINCT
FIRST_VALUE(ENAME) OVER (PARTITION BY DEPTNO ORDER BY SAL DESC)
FROM EMP;
📌 NTILE(数据切片)
sql
-- 每个部门薪资分3档,取高薪档
SELECT * FROM (
SELECT NTILE(3) OVER (PARTITION BY DEPTNO ORDER BY SAL DESC) AS NT, E.*
FROM EMP
) WHERE NT = 1;
📌 聚合开窗(累计/占比)
| 写法 | 效果 |
|---|---|
SUM() OVER(PARTITION BY 组) |
每组总和(每行相同) |
SUM() OVER(ORDER BY 日期) |
累计求和 |
SUM() OVER(PARTITION BY 组 ORDER BY 日期) |
组内累计 |
sql
-- 部门薪资占比
SELECT SAL / SUM(SAL) OVER (PARTITION BY DEPTNO) FROM EMP;
-- 累计人力成本
SELECT SUM(SAL) OVER (ORDER BY HIREDATE) FROM EMP;
⚡ 执行顺序
text
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
↑
窗口函数在这里计算
⚠️ 核心要点
| ✅ 可以 | ❌ 不可以 |
|---|---|
| 写在 SELECT 后 | 用在 WHERE/HAVING |
| PARTITION BY 分组 | 直接使用别名过滤 |
| 每行返回一个值 | 合并多行 |
🎯 场景速查
| 需求 | 用什么 |
|---|---|
| 排名 / TOP N | ROW_NUMBER |
| 同环比 | LAG / LEAD |
| 每组第一名 | FIRST_VALUE |
| 分档/分桶 | NTILE |
| 累计求和 | SUM OVER(ORDER BY) |
| 占比 | SUM OVER(PARTITION BY) |
💡 记忆口诀:排名用ROW,上下用LAG,切片NTILE,累计SUM OVER