Oracle 分析函数使用总结
一、概述
1.1 什么是分析函数?
分析函数(Analytic Function)是 Oracle 提供的一类强大的数据处理工具。顾名思义,分析函数是在主查询结果集的基础上进行进一步分析计算,例如:
- 排名和分位数分析
- 访问当前行之前或之后的行数据
分析函数与普通聚合函数(GROUP BY)最核心的区别在于:分析函数不会减少结果集的行数,每一行原始数据都会保留。
1.2 什么是数据窗口(Data Window)?
分析函数的核心概念是数据窗口,它定义了一个分析范围。可以这样理解:
数据窗口 = 主查询结果集按照特定规则划分的数据子集
数据窗口由三部分组成:
- 分区(Partition) :通过
PARTITION BY将数据集划分为多个独立的组。例如,PARTITION BY dept_id会将数据集按部门分成多个窗口,每个窗口独立计算。 - 排序(Order) :通过
ORDER BY定义窗口内的数据顺序。对于需要排序的分析函数(如排名、累计等),这一步至关重要。 - 数据窗口(windowing_clause):定义分析的范围边界(控制参与计算的行范围)
1.3 分析函数与 GROUP BY 的区别与联系
| 对比维度 | 分析函数 | GROUP BY 聚合 |
|---|---|---|
| 结果行数 | 与主查询行数相同 | 按分组维度聚合,行数减少 |
| 功能范围 | 聚合 + 更多分析能力 | 主要是聚合 |
| 数据粒度 | 保留原始明细 + 分析结果 | 仅保留汇总结果 |
| 适用场景 | 需要同时查看明细和汇总 | 只需要汇总报告 |
sql
-- GROUP BY:行数减少为部门数量
SELECT dept_id, SUM(salary) FROM employees GROUP BY dept_id;
-- 分析函数:行数不变,每行都附带部门汇总
SELECT emp_id, dept_id, salary,
SUM(salary) OVER (PARTITION BY dept_id) AS dept_total
FROM employees;
二、分析函数语法
所有分析函数遵循统一语法结构:
sql
function_name([arguments]) OVER (
[PARTITION BY partition_expression, ...]
[ORDER BY sort_expression [ASC|DESC] [NULLS FIRST|NULLS LAST], ...]
[windowing_clause]
)
三大组件各司其职:
| 组件 | 作用 | 类比 |
|---|---|---|
| PARTITION BY | 将结果集划分为多个逻辑分区,函数在每个分区内独立计算 | 类似 GROUP BY,但不压缩行 |
| ORDER BY | 定义分区内行的处理顺序 | 排名、累计计算的基石 |
| windowing_clause | 精确控制参与计算的行范围(窗口帧) | 决定"看几行" |
窗口子句(Window_clause )
窗口帧通过 ROWS 或 RANGE 关键字定义计算边界:
sql
-- 窗口边界语法
[ROWS | RANGE] BETWEEN start_expr AND end_expr
-- start_expr 可选值
UNBOUNDED PRECEDING -- 从分区第一行开始
CURRENT ROW -- 从当前行开始
n PRECEDING -- 往前 n 行/值
n FOLLOWING -- 往后 n 行/值
-- end_expr 可选值
UNBOUNDED FOLLOWING -- 到分区最后一行结束
CURRENT ROW -- 到当前行结束
n PRECEDING -- 往前 n 行/值
n FOLLOWING -- 往后 n 行/值
ROWS 按物理行偏移,严格取固定行数,不管排序列值是否重复。
RANGE 按排序列的值范围定义窗口,所有值落在范围内的行都参与计算,重复值会被"吞并"。
常用边界取值:
| 边界 | 含义 |
|---|---|
UNBOUNDED PRECEDING |
分区第一行 |
n PRECEDING |
当前行之前第 n 行/值 |
CURRENT ROW |
当前行 |
n FOLLOWING |
当前行之后第 n 行/值 |
UNBOUNDED FOLLOWING |
分区最后一行 |
默认行为 :指定了 ORDER BY 但省略窗口帧时,默认范围为 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(从分区首行到当前行)。完全省略 ORDER BY 和窗口帧时,范围为整个分区。
执行顺序
分析函数在 WHERE、GROUP BY、HAVING 之后 执行,在最终 ORDER BY 之前 执行。因此不能在 WHERE 中直接引用分析函数别名,需要嵌套子查询或 CTE。
三、分析函数分类总览
| 类别 | 函数 | 核心用途 |
|---|---|---|
| 排名函数 | ROW_NUMBER, RANK, DENSE_RANK, NTILE | 生成排名序号 |
| 聚合窗口函数 | SUM, AVG, COUNT, MAX, MIN | 窗口内聚合计算 |
| 偏移函数 | LAG, LEAD | 获取前/后行的值 |
| 首尾值函数 | FIRST_VALUE, LAST_VALUE, NTH_VALUE | 获取窗口内指定位置的值 |
| 分布函数 | CUME_DIST, PERCENT_RANK, PERCENTILE_CONT, PERCENTILE_DISC | 计算累积分布和百分位 |
| 报表函数 | RATIO_TO_REPORT | 计算占比 |
| FIRST/LAST 聚合 | KEEP (DENSE_RANK FIRST/LAST) | 获取排序组内首尾聚合值 |
| 统计函数 | VARIANCE, STDDEV, CORR, COVAR_POP, REGR_* | 方差、标准差、相关系数、回归 |
| 字符串聚合 | LISTAGG | 将多行字符串拼接为一行 |
四、性能优化建议
| 建议 | 说明 |
|---|---|
| 索引设计 | 在 PARTITION BY 和 ORDER BY 涉及的列上建组合索引,避免全表排序 |
| 避免无界窗口 | 慎用 UNBOUNDED PRECEDING/FOLLOWING,大数据量下可能导致内存溢出 |
| 显式写 ROWS | 默认 RANGE 在重复值场景下可能吞并大量行,显式写 ROWS 更可控 |
| 避免多层嵌套 | 在分析函数结果上再套分析函数需多层子查询,执行计划复杂度陡增 |
| 减少 PARTITION BY 列数 | 分区列越多,排序开销越大 |
| 先过滤再分析 | 将 WHERE 条件放在子查询中,减少进入分析函数的行数 |
| 内存需求 | 分析函数在 PGA 中计算,建议合理配置 PGA 大小 |
| 并行执行 | 分析函数支持并行执行,合理设置并行度可提升性能 |
| 谓词前推 | ORDER BY 子句的列不能安全前推,但 PARTITION BY 列可以 |
| 绑定变量 | 分区列和排序列不能使用绑定变量,如有需要请使用动态 SQL |
| 执行计划 | 分析函数通常会产生 WINDOW SORT 操作,注意优化 |
五、排名函数
5.1 ROW_NUMBER()
为每行分配唯一且连续的序号,即使排序值相同也绝不重复。
sql
-- 案例:每个部门内按薪资降序编号
SELECT ename, deptno, sal,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) AS rn
FROM emp;
ENAME DEPTNO SAL RN
---------- ------ ------ -----
KING 10 5000 1 ← 部门10薪资最高
CLARK 10 2450 2
MILLER 10 1300 3
SCOTT 20 3000 1 ← 部门20薪资最高
FORD 20 3000 2 ← 薪资与SCOTT相同,但序号不同
JONES 20 2975 3
...
典型用途:Top-N 查询、分页去重、为无序数据生成唯一序号。
sql
-- Top-N 查询:取每个部门薪资前2名
SELECT * FROM (
SELECT ename, deptno, sal,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) AS rn
FROM emp
) WHERE rn <= 2;
ENAME DEPTNO SAL RN
---------- ---------- ---------- ----------
KING 10 5000 1
CLARK 10 2450 2
SCOTT 20 3000 1
FORD 20 3000 2
BLAKE 30 2850 1
ALLEN 30 1600 2
5.2 RANK()
相同排序值赋予相同排名 ,后续排名跳跃(如 1, 2, 2, 4)。
sql
SELECT ename, deptno, sal,
RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS rnk
FROM emp
WHERE deptno = 20;
ENAME DEPTNO SAL RNK
---------- ------ ------ -----
SCOTT 20 3000 1 ← 并列第1
FORD 20 3000 1
JONES 20 2975 3 ← 跳过了2
ADAMS 20 1100 4
SMITH 20 800 5
5.3 DENSE_RANK()
相同排序值赋予相同排名 ,后续排名不跳跃(如 1, 2, 2, 3)。
sql
SELECT ename, deptno, sal,
DENSE_RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) AS drnk
FROM emp
WHERE deptno = 20;
ENAME DEPTNO SAL DRNK
---------- ------ ------ -----
SCOTT 20 3000 1
FORD 20 3000 1
JONES 20 2975 2 ← 连续,没有跳过
ADAMS 20 1100 3
SMITH 20 800 4
5.4 三者对比
| 函数 | 相同值处理 | 后续序号 | 示例 |
|---|---|---|---|
| ROW_NUMBER | 不并列 | 严格递增 | 1, 2, 3, 4 |
| RANK | 并列 | 跳跃 | 1, 1, 3, 4 |
| DENSE_RANK | 并列 | 连续 | 1, 1, 2, 3 |
5.5 NTILE(n)
该分析函数的窗口子句,必须包含order by,否则报错:ORDER BY expression in the window specification
将分区内有序数据均匀分为 n 个桶,返回行所在桶号(1 到 n)。
sql
-- 案例:将员工按薪资分为4个等级(四分位)
SELECT ename, sal,
NTILE(4) OVER (ORDER BY sal DESC) AS quartile
FROM emp;
ENAME SAL QUARTILE
---------- ----- --------
KING 5000 1 ← 第一梯队(最高25%)
SCOTT 3000 1
FORD 3000 2 ← 第二梯队
JONES 2975 2
...
SMITH 800 4 ← 第四梯队(最低25%)
典型用途:
1) 客户价值分层(高中低)、成绩分档、数据分桶统计。
2)如果over窗口子句,不加任何内容,则表示将按照整个表中的数据行,随机划分为N个桶,每个桶中的行数据,基本一致(偏少很少),在需要将表中的数据行进行随机分类时,很有用,例如
-- 案例:将员工表数据按照rowid划分为4个等级(四分位),
-- 这在将表中数据按桶,传递给下一个程序(或语句)使用时,非常方便
SELECT ename, sal,NTILE(4) OVER (order by rowid) AS N_flag
FROM emp;
ENAME SAL N_FLAG
---------- ---------- ----------
SMITH 800 1
ALLEN 1600 1
WARD 1250 1
JONES 2975 1
MARTIN 1250 2
BLAKE 2850 2
CLARK 2450 2
SCOTT 3000 2
KING 5000 3
TURNER 1500 3
ADAMS 1100 3
JAMES 950 4
FORD 3000 4
MILLER 1300 4
六、聚合窗口函数
SUM、AVG、COUNT、MAX、MIN 等聚合函数配合 OVER 子句,在保留原始行的同时计算窗口内的聚合值。
6.1 分区聚合(不加 ORDER BY)
对整个分区计算聚合值,每行结果相同:
sql
-- 每个员工的薪资 + 该部门的总薪资和平均薪资
SELECT ename, deptno, sal,
SUM(sal) OVER (PARTITION BY deptno) AS dept_total,
AVG(sal) OVER (PARTITION BY deptno) AS dept_avg,
COUNT(*) OVER (PARTITION BY deptno) AS dept_count,
MAX(sal) OVER (PARTITION BY deptno) AS dept_max,
MIN(sal) OVER (PARTITION BY deptno) AS dept_min
FROM emp;
ENAME DEPTNO SAL DEPT_TOTAL DEPT_AVG DEPT_COUNT DEPT_MAX DEPT_MIN
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
CLARK 10 2450 8750 2916.66667 3 5000 1300
KING 10 5000 8750 2916.66667 3 5000 1300
MILLER 10 1300 8750 2916.66667 3 5000 1300
JONES 20 2975 10875 2175 5 3000 800
FORD 20 3000 10875 2175 5 3000 800
ADAMS 20 1100 10875 2175 5 3000 800
SMITH 20 800 10875 2175 5 3000 800
SCOTT 20 3000 10875 2175 5 3000 800
WARD 30 1250 9400 1566.66667 6 2850 950
TURNER 30 1500 9400 1566.66667 6 2850 950
ALLEN 30 1600 9400 1566.66667 6 2850 950
JAMES 30 950 9400 1566.66667 6 2850 950
BLAKE 30 2850 9400 1566.66667 6 2850 950
MARTIN 30 1250 9400 1566.66667 6 2850 950
6.2 累计求和(加 ORDER BY,不加窗口帧)
从分区首行到当前行,逐行累加:
sql
-- 每个部门内按入职时间累计薪资
SELECT ename, deptno, hiredate, sal,
SUM(sal) OVER (PARTITION BY deptno ORDER BY hiredate) AS running_total
FROM emp
WHERE deptno = 10;
ENAME DEPTNO HIREDATE SAL RUNNING_TOTAL
------ ------ ---------- ----- -------------
CLARK 10 1981-06-09 2450 2450 ← 第1行:只有CLARK
KING 10 1981-11-17 5000 7450 ← 第2行:CLARK+KING
MILLER 10 1982-01-23 1300 8750 ← 第3行:CLARK+KING+MILLER
注意:这里没有显式写窗口帧,Oracle 默认使用
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。如果hiredate有重复值,RANGE 会将相同日期的行全部纳入,可能导致累计值"跳跃"。建议显式写ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW以确保按物理行累计。
6.3 移动平均(显式窗口帧)
sql
-- 每个部门内按入职时间,计算当前行及前后各1行的3行移动平均薪资
SELECT ename, deptno, hiredate, sal,
ROUND(AVG(sal) OVER (
PARTITION BY deptno
ORDER BY hiredate
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
), 2) AS moving_avg_3
FROM emp
WHERE deptno = 10;
ENAME DEPTNO HIREDATE SAL MOVING_AVG_3
------ ------ ---------- ----- ------------
CLARK 10 1981-06-09 2450 3725.00 ← (2450+5000)/2,前面没行
KING 10 1981-11-17 5000 2916.67 ← (2450+5000+1300)/3
MILLER 10 1982-01-23 1300 3150.00 ← (5000+1300)/2,后面没行
4.4 全表聚合(不加 PARTITION BY)
省略 PARTITION BY 时,整个结果集视为一个分区:
sql
-- 每个员工的薪资 + 全公司总薪资 + 占全公司的比例
SELECT ename, sal,
SUM(sal) OVER () AS grand_total,
ROUND(sal / SUM(sal) OVER () * 100, 2) AS pct_of_total
FROM emp;
ENAME SAL GRAND_TOTAL PCT_OF_TOTAL
---------- ---------- ----------- ------------
SMITH 800 29025 2.76
ALLEN 1600 29025 5.51
WARD 1250 29025 4.31
JONES 2975 29025 10.25
MARTIN 1250 29025 4.31
BLAKE 2850 29025 9.82
CLARK 2450 29025 8.44
SCOTT 3000 29025 10.34
KING 5000 29025 17.23
TURNER 1500 29025 5.17
ADAMS 1100 29025 3.79
JAMES 950 29025 3.27
FORD 3000 29025 10.34
MILLER 1300 29025 4.48
七、偏移函数
7.1 LAG(expr, offset, default)
获取当前行之前第 offset 行的值。offset 默认为 1,default 默认为 NULL。
sql
-- 案例:计算每个员工与前一入职员工的薪资差额
SELECT ename, deptno, hiredate, sal,
LAG(sal, 1, 0) OVER (PARTITION BY deptno ORDER BY hiredate) AS prev_sal,
sal - LAG(sal, 1, 0) OVER (PARTITION BY deptno ORDER BY hiredate) AS sal_diff
FROM emp
WHERE deptno = 10;
ENAME DEPTNO HIREDATE SAL PREV_SAL SAL_DIFF
------ ------ ---------- ----- -------- --------
CLARK 10 1981-06-09 2450 0 2450 ← 第1行,无前行,取默认值0
KING 10 1981-11-17 5000 2450 2550 ← 5000 - 2450
MILLER 10 1982-01-23 1300 5000 -3700 ← 1300 - 5000
典型用途:
1)环比分析(本月 vs 上月)、状态变化检测、趋势判断。
2)在进行监控数据分析时,如果需要比对前一天的差值,这个函数很方便,例如在数据库中,有采集表每天的大小信息,如果我想分析某张表,每一天的增长量,这个函数就可以很好的派上用场了!
7.2 LEAD(expr, offset, default)
获取当前行之后第 offset 行的值。语法与 LAG 相同。
sql
-- 案例:计算每次订单与下一次订单的间隔天数
SELECT ename, deptno, hiredate, sal,
LEAD(sal, 1, 0) OVER (PARTITION BY deptno ORDER BY hiredate) AS last_sal,
sal - LEAD(sal, 1, 0) OVER (PARTITION BY deptno ORDER BY hiredate) AS sal_diff
FROM emp
WHERE deptno = 20;
ENAME DEPTNO HIREDATE SAL LAST_SAL SAL_DIFF
---------- ---------- ------------ ---------- ---------- ----------
SMITH 20 17-DEC-80 800 2975 -2175
JONES 20 02-APR-81 2975 3000 -25
FORD 20 03-DEC-81 3000 3000 0
SCOTT 20 19-APR-87 3000 1100 1900
ADAMS 20 23-MAY-87 1100 0 1100 --最后一行,无下一行数据,取默认值0
7.3 LAG 与 LEAD 的 offset 参数
支持跨多行取值:
sql
-- 获取前2行和后2行的值
SELECT ename, sal,
LAG(sal, 2) OVER (ORDER BY hiredate) AS sal_2_before,
LAG(sal, 1) OVER (ORDER BY hiredate) AS sal_1_before,
sal,
LEAD(sal, 1) OVER (ORDER BY hiredate) AS sal_1_after,
LEAD(sal, 2) OVER (ORDER BY hiredate) AS sal_2_after
FROM emp;
ENAME SAL SAL_2_BEFORE SAL_1_BEFORE SAL SAL_1_AFTER SAL_2_AFTER
---------- ---------- ------------ ------------ ---------- ----------- -----------
SMITH 800 800 1600 1250
ALLEN 1600 800 1600 1250 2975
WARD 1250 800 1600 1250 2975 2850
JONES 2975 1600 1250 2975 2850 2450
BLAKE 2850 1250 2975 2850 2450 1500
CLARK 2450 2975 2850 2450 1500 1250
TURNER 1500 2850 2450 1500 1250 5000
MARTIN 1250 2450 1500 1250 5000 950
KING 5000 1500 1250 5000 950 3000
JAMES 950 1250 5000 950 3000 1300
FORD 3000 5000 950 3000 1300 3000
MILLER 1300 950 3000 1300 3000 1100
SCOTT 3000 3000 1300 3000 1100
ADAMS 1100 1300 3000 1100
八、首尾值函数
8.1 FIRST_VALUE(expr)
返回窗口内第一行的指定表达式值。
sql
-- 案例:每个部门薪资最高的员工姓名和薪资
SELECT ename, deptno, sal,
FIRST_VALUE(ename) OVER (
PARTITION BY deptno ORDER BY sal DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS top_earner,
FIRST_VALUE(sal) OVER (
PARTITION BY deptno ORDER BY sal DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS top_salary
FROM emp;
ENAME DEPTNO SAL TOP_EARNER TOP_SALARY
---------- ---------- ---------- ---------- ----------
KING 10 5000 KING 5000
CLARK 10 2450 KING 5000
MILLER 10 1300 KING 5000
SCOTT 20 3000 SCOTT 3000
FORD 20 3000 SCOTT 3000
JONES 20 2975 SCOTT 3000
ADAMS 20 1100 SCOTT 3000
SMITH 20 800 SCOTT 3000
BLAKE 30 2850 BLAKE 2850
ALLEN 30 1600 BLAKE 2850
TURNER 30 1500 BLAKE 2850
MARTIN 30 1250 BLAKE 2850
WARD 30 1250 BLAKE 2850
JAMES 30 950 BLAKE 2850
8.2 LAST_VALUE(expr)
返回窗口内最后一行的指定表达式值。
常见陷阱 :
LAST_VALUE的默认窗口帧是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,这意味着"最后一行"就是当前行本身!要获取整个分区的最后一个值,必须显式指定窗口帧。
sql
-- 错误:返回的就是当前行的值
LAST_VALUE(sal) OVER (PARTITION BY deptno ORDER BY sal)
-- 正确:显式指定整个分区范围
LAST_VALUE(sal) OVER (
PARTITION BY deptno ORDER BY sal
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)
-- 案例:每个部门薪资高的员工姓名和薪资
-- 使用LAST_VALUE分析函数,ORDER BY sal排序,不指定降序,因此窗口内最后一就是薪资最高的员工姓名和薪资
SELECT ename, deptno, sal,
LAST_VALUE(ename) OVER (
PARTITION BY deptno ORDER BY sal
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS top_earner,
LAST_VALUE(sal) OVER (
PARTITION BY deptno ORDER BY sal
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS top_salary
FROM emp;
ENAME DEPTNO SAL TOP_EARNER TOP_SALARY
---------- ---------- ---------- ---------- ----------
MILLER 10 1300 KING 5000
CLARK 10 2450 KING 5000
KING 10 5000 KING 5000
SMITH 20 800 FORD 3000
ADAMS 20 1100 FORD 3000
JONES 20 2975 FORD 3000
SCOTT 20 3000 FORD 3000
FORD 20 3000 FORD 3000
JAMES 30 950 BLAKE 2850
MARTIN 30 1250 BLAKE 2850
WARD 30 1250 BLAKE 2850
TURNER 30 1500 BLAKE 2850
ALLEN 30 1600 BLAKE 2850
BLAKE 30 2850 BLAKE 2850
8.3 NTH_VALUE(expr, n)
返回窗口内第 n 行的指定表达式值。同样需要注意窗口帧范围。
sql
-- 案例:每个部门薪资第2高的员工
SELECT ename, deptno, sal,
NTH_VALUE(ename, 2) OVER (
PARTITION BY deptno ORDER BY sal DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS second_highest
FROM emp;
ENAME DEPTNO SAL SECOND_HIG
---------- ---------- ---------- ----------
KING 10 5000 CLARK
CLARK 10 2450 CLARK
MILLER 10 1300 CLARK
SCOTT 20 3000 FORD
FORD 20 3000 FORD
JONES 20 2975 FORD
ADAMS 20 1100 FORD
SMITH 20 800 FORD
BLAKE 30 2850 ALLEN
ALLEN 30 1600 ALLEN
TURNER 30 1500 ALLEN
MARTIN 30 1250 ALLEN
WARD 30 1250 ALLEN
JAMES 30 950 ALLEN
九、分布函数
9.1 CUME_DIST()
计算累积分布:小于等于当前值的行数占分区总行数的比例。结果范围 (0, 1]。
sql
SELECT ename, sal,
CUME_DIST() OVER (ORDER BY sal) AS cume_dist
FROM emp;
ENAME SAL CUME_DIST
---------- ---------- ----------
SMITH 800 .071428571 ← 1/14,14人中最小的
JAMES 950 .142857143
ADAMS 1100 .214285714
WARD 1250 .357142857
MARTIN 1250 .357142857
MILLER 1300 .428571429
TURNER 1500 .5
ALLEN 1600 .571428571
CLARK 2450 .642857143
BLAKE 2850 .714285714
JONES 2975 .785714286
SCOTT 3000 .928571429
FORD 3000 .928571429
KING 5000 1 ← 14/14,最大的
典型用途:百分位分析、"该员工薪资超过了多少比例的人"。
9.2 PERCENT_RANK()
计算百分比排名 :(rank - 1) / (total_rows - 1)。结果范围 0, 1。
sql
SELECT ename, sal,
PERCENT_RANK() OVER (ORDER BY sal) AS pct_rank
FROM emp;
ENAME SAL PCT_RANK
---------- ---------- ----------
SMITH 800 0 ← 最低的,排名百分比为0
JAMES 950 .076923077
ADAMS 1100 .153846154
WARD 1250 .230769231
MARTIN 1250 .230769231
MILLER 1300 .384615385
TURNER 1500 .461538462
ALLEN 1600 .538461538
CLARK 2450 .615384615
BLAKE 2850 .692307692
JONES 2975 .769230769
SCOTT 3000 .846153846
FORD 3000 .846153846
KING 5000 1 ← 最高的,排名百分比为1
9.3 PERCENTILE_CONT(n) 与 PERCENTILE_DISC(n)
逆百分位函数,返回与指定百分位对应的值。
PERCENTILE_CONT(n):连续分布,返回插值计算的结果(可以是小数)PERCENTILE_DISC(n):离散分布,返回实际存在的值
sql
-- 计算各部门薪资的中位数(第50百分位)
SELECT deptno,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY sal) AS median_cont,
PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY sal) AS median_disc
FROM emp
GROUP BY deptno;
DEPTNO MEDIAN_CONT MEDIAN_DISC
------ ----------- -----------
10 2450 2450
20 2187.5 2975 ← CONT返回插值,DISC返回实际值
30 1550 1500
十、报表函数
RATIO_TO_REPORT(expr)
计算当前行值占分区总和的比例,无需手动关联子查询求总计。
sql
-- 案例:每个员工薪资占部门总薪资的比例
SELECT ename, deptno, sal,
ROUND(RATIO_TO_REPORT(sal) OVER (PARTITION BY deptno) * 100, 2) AS pct_of_dept
FROM emp;
ENAME DEPTNO SAL PCT_OF_DEPT
---------- ---------- ---------- -----------
CLARK 10 2450 28
KING 10 5000 57.14 ← 5000/8750*100
MILLER 10 1300 14.86
JONES 20 2975 27.36
FORD 20 3000 27.59
ADAMS 20 1100 10.11
SMITH 20 800 7.36
SCOTT 20 3000 27.59
WARD 30 1250 13.3
TURNER 30 1500 15.96
ALLEN 30 1600 17.02
JAMES 30 950 10.11
BLAKE 30 2850 30.32
MARTIN 30 1250 13.3
典型用途:市场份额计算、成本分摊、KPI 达成率、占比报表。
十一、FIRST/LAST 聚合变体
结合 KEEP (DENSE_RANK FIRST/LAST ORDER BY ...) 语法,在分组内依据排序逻辑精准提取首个/末个对应值。解决传统 MAX/MIN 无法关联非聚合字段的经典难题。
sql
-- 案例:每个部门薪资最高者的姓名
SELECT deptno,
MAX(sal) AS max_sal,
MIN(ename) KEEP (DENSE_RANK FIRST ORDER BY sal DESC) AS top_earner
FROM emp
GROUP BY deptno;
DEPTNO MAX_SAL TOP_EARNER
------ ------- ----------
10 5000 KING
20 3000 FORD ← SCOTT和FORD薪资并列,取ename字典序最小的
30 2850 BLAKE
与 FIRST_VALUE 的区别:
FIRST_VALUE是分析函数,保留所有行KEEP (DENSE_RANK FIRST)是聚合函数变体,配合GROUP BY使用,压缩行数
十二、统计函数
Oracle 提供了一系列窗口化的统计函数,支持在窗口内计算统计学指标。
12.1 方差与标准差
sql
-- 每个部门薪资的方差和标准差
SELECT ename, deptno, sal,
VARIANCE(sal) OVER (PARTITION BY deptno) AS dept_var,
STDDEV(sal) OVER (PARTITION BY deptno) AS dept_stddev
FROM emp;
ENAME DEPTNO SAL DEPT_VAR DEPT_STDDEV
---------- ---------- ---------- ---------- -----------
CLARK 10 2450 3585833.33 1893.62967
KING 10 5000 3585833.33 1893.62967
MILLER 10 1300 3585833.33 1893.62967
JONES 20 2975 1261875 1123.3321
FORD 20 3000 1261875 1123.3321
ADAMS 20 1100 1261875 1123.3321
SMITH 20 800 1261875 1123.3321
SCOTT 20 3000 1261875 1123.3321
WARD 30 1250 446666.667 668.331255
TURNER 30 1500 446666.667 668.331255
ALLEN 30 1600 446666.667 668.331255
JAMES 30 950 446666.667 668.331255
BLAKE 30 2850 446666.667 668.331255
MARTIN 30 1250 446666.667 668.331255
12.2 相关系数与协方差
sql
-- 按月计算销售额与销量的累积相关系数
SELECT t.calendar_month_number,
CORR(SUM(s.amount_sold), SUM(s.quantity_sold))
OVER (ORDER BY t.calendar_month_number) AS cum_corr
FROM sales s, times t
WHERE s.time_id = t.time_id
AND calendar_year = 2000
GROUP BY t.calendar_month_number
ORDER BY t.calendar_month_number;
CALENDAR_MONTH_NUMBER CUM_CORR
--------------------- ----------
1
2 -1
3 .497217523
4 .79761256
5 .773560058
6 .827475438
7 .797660711
8 .77216009
9 .741400246
10 .787357879
11 .768457303
12 .383732282
12.3 线性回归函数
sql
-- 计算销售额随时间的线性回归斜率和截距
SELECT calendar_month_number,
REGR_SLOPE(SUM(amount_sold), calendar_month_number)
OVER () AS slope,
REGR_INTERCEPT(SUM(amount_sold), calendar_month_number)
OVER () AS intercept,
REGR_R2(SUM(amount_sold), calendar_month_number)
OVER () AS r_squared
FROM sales s, times t
WHERE s.time_id = t.time_id
AND calendar_year = 2000
GROUP BY calendar_month_number;
十三、字符串聚合函数
LISTAGG(expr, delimiter)
将分区内多行字符串拼接为一个字符串,用指定分隔符连接。
sql
-- 案例:将每个部门的员工姓名拼接为一行
col employees format a50
SELECT deptno,
LISTAGG(ename, ', ') WITHIN GROUP (ORDER BY ename) AS employees
FROM emp
GROUP BY deptno;
DEPTNO EMPLOYEES
------ ------------------------------------------
10 CLARK, KING, MILLER
20 ADAMS, FORD, JONES, SCOTT, SMITH
30 ALLEN, BLAKE, JAMES, MARTIN, TURNER, WARD
也可以作为分析函数使用(保留所有行):
sql
col dept_members format a50
SELECT ename, deptno,
LISTAGG(ename, ', ') WITHIN GROUP (ORDER BY ename)
OVER (PARTITION BY deptno) AS dept_members
FROM emp;
ENAME DEPTNO DEPT_MEMBERS
---------- ---------- --------------------------------------------------
CLARK 10 CLARK, KING, MILLER
KING 10 CLARK, KING, MILLER
MILLER 10 CLARK, KING, MILLER
ADAMS 20 ADAMS, FORD, JONES, SCOTT, SMITH
FORD 20 ADAMS, FORD, JONES, SCOTT, SMITH
JONES 20 ADAMS, FORD, JONES, SCOTT, SMITH
SCOTT 20 ADAMS, FORD, JONES, SCOTT, SMITH
SMITH 20 ADAMS, FORD, JONES, SCOTT, SMITH
ALLEN 30 ALLEN, BLAKE, JAMES, MARTIN, TURNER, WARD
BLAKE 30 ALLEN, BLAKE, JAMES, MARTIN, TURNER, WARD
JAMES 30 ALLEN, BLAKE, JAMES, MARTIN, TURNER, WARD
MARTIN 30 ALLEN, BLAKE, JAMES, MARTIN, TURNER, WARD
TURNER 30 ALLEN, BLAKE, JAMES, MARTIN, TURNER, WARD
WARD 30 ALLEN, BLAKE, JAMES, MARTIN, TURNER, WARD