Oracle 分析函数使用总结

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
相关推荐
updayday8541 小时前
印染产线电脑进程异常占用资源却要逐台排查 - Ping64 远程进程管理
大数据·网络·数据库·安全·智能路由器
QYRdata1 小时前
年复合增长率8.2%!2026-2032年数据库开发服务赛道增长动能持续释放
数据库·服务发现·数据库开发·恒州博智
坊钰1 小时前
【LangGraph框架入门级】12. 人机交互:interrupt 中断与 Command 恢复
开发语言·数据库·python·人机交互·ai编程·langgraph
龙腾AI白云2 小时前
世界模型与智能体协同推理逻辑
大数据·数据库·知识图谱·pygame
ben9518chen2 小时前
数据库系统概述
数据库
花间相见2 小时前
【计算基础|MySQL03】【上手】多表查询与聚合
数据库·mysql
小羊没烦恼!2 小时前
T-SQL查询高级—SQL Server索引中的碎片和填充因子
开发语言·前端·javascript·数据库·后端·python·web安全
lv__pf2 小时前
redis【TL redis(6)】
数据库·redis·缓存