文章目录
- 一、分类总览
- 二、窗口函数基础
- 三、聚合统计类
- [四、排名 / 分组类](#四、排名 / 分组类)
- 五、前后取值类
- [六、分布 / 百分位类](#六、分布 / 百分位类)
- 七、随机抽样类
- 八、最容易混淆的函数对比
- 九、常用案例
- 十、参考资料
一、分类总览
1、函数分类
| 分类 | 主要解决的问题 | 代表函数 |
|---|---|---|
| 1、聚合统计类 | 在窗口内求和、均值、最大值、最小值、中位数、标准差 | SUM、AVG、COUNT、MAX、MIN、MEDIAN、STDDEV、STDDEV_SAMP |
| 2、排名 / 分组类 | 对分区内数据编号、排名、分桶 | ROW_NUMBER、RANK、DENSE_RANK、NTILE |
| 3、前后取值类 | 获取第一条、最后一条、第 N 条、前后 N 行 | FIRST_VALUE、LAST_VALUE、NTH_VALUE、LAG、LEAD |
| 4、分布 / 百分位类 | 计算累计分布、百分比排名、百分位 | CUME_DIST、PERCENT_RANK、PERCENTILE_CONT、PERCENTILE_DISC |
| 5、随机抽样类 | 在窗口 / 分区内随机抽样 | CLUSTER_SAMPLE |
2、全部函数
| 分类 | 函数 | 功能 |
|---|---|---|
| 1、聚合统计类 | AVG |
计算窗口内平均值 |
| 1、聚合统计类 | COUNT |
计算窗口内记录数 |
| 1、聚合统计类 | MAX |
计算窗口内最大值 |
| 1、聚合统计类 | MEDIAN |
计算窗口内中位数 |
| 1、聚合统计类 | MIN |
计算窗口内最小值 |
| 1、聚合统计类 | STDDEV |
计算窗口内总体标准差 |
| 1、聚合统计类 | STDDEV_SAMP |
计算窗口内样本标准差 |
| 1、聚合统计类 | SUM |
计算窗口内总和 |
| 2、排名 / 分组类 | DENSE_RANK |
连续排名,相同值同名次且不跳号 |
| 2、排名 / 分组类 | NTILE |
将有序数据切成 N 份并返回分组编号 |
| 2、排名 / 分组类 | RANK |
排名,相同值同名次但可能跳号 |
| 2、排名 / 分组类 | ROW_NUMBER |
为分区内每行生成唯一行号 |
| 3、前后取值类 | FIRST_VALUE |
获取当前窗口第一条数据的值 |
| 3、前后取值类 | LAG |
获取当前行之前第 N 行的值 |
| 3、前后取值类 | LAST_VALUE |
获取当前窗口最后一条数据的值 |
| 3、前后取值类 | LEAD |
获取当前行之后第 N 行的值 |
| 3、前后取值类 | NTH_VALUE |
获取当前窗口第 N 条数据的值 |
| 4、分布 / 百分位类 | CUME_DIST |
计算累计分布 |
| 4、分布 / 百分位类 | PERCENT_RANK |
计算百分比排名 |
| 4、分布 / 百分位类 | PERCENTILE_CONT |
计算连续型精确百分位,可插值 |
| 4、分布 / 百分位类 | PERCENTILE_DISC |
计算离散型百分位,返回实际值 |
| 5、随机抽样类 | CLUSTER_SAMPLE |
在窗口内随机抽样,返回是否被抽中 |
二、窗口函数基础
| 关键字 | 功能 |
|---|---|
OVER |
声明窗口函数 |
PARTITION BY |
将数据划分为多个独立分区 |
ORDER BY |
定义分区内的排序顺序 |
ROWS |
按行数定义窗口范围 |
RANGE |
按排序字段的值范围定义窗口 |
GROUPS |
按相同排序值组成的组定义窗口 |
PRECEDING |
当前行之前 |
FOLLOWING |
当前行之后 |
CURRENT ROW |
当前行 |
UNBOUNDED PRECEDING |
分区起点 |
UNBOUNDED FOLLOWING |
分区终点 |
1、定义
窗口函数会在当前行对应的"窗口"中进行计算,同时保留原始明细行。
基本结构:
sql
函数(...) OVER (
PARTITION BY 分组字段
ORDER BY 排序字段
ROWS BETWEEN 起点 AND 终点
)
2、区别
| 关键字 | 核心含义 |
|---|---|
PARTITION BY |
决定"和谁一起算" |
ORDER BY |
决定"先后顺序" |
ROWS |
按具体行数确定范围 |
RANGE |
按排序字段值的范围确定窗口 |
GROUPS |
按相同排序值形成的组确定窗口 |
窗口函数与普通 GROUP BY 最大区别:
GROUP BY会把多行聚合为更少的行;窗口函数保留原始行,只在每行后面增加计算结果。
官方使用限制:
- 窗口函数只能出现在
SELECT中。 - 窗口函数内部不能嵌套窗口函数和聚合函数。
- 窗口函数不能与同级别聚合函数直接混用。
FILTER只适用于聚合类窗口函数,例如COUNT、SUM、AVG、MAX、MIN等。
3、简单记忆
PARTITION BY= 分组。
ORDER BY= 排序。
ROWS= 按行。
RANGE= 按值范围。
GROUPS= 按同值组。
OVER= 在这个窗口上计算。
4、案例
sql
-- 结果:每个部门内部按工资升序计算累计工资
SELECT deptno, ename, sal, SUM(sal) OVER(PARTITION BY deptno ORDER BY sal ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_sal FROM emp;
-- 结果:只让 value > 100 的记录参与窗口 SUM 计算
SELECT key, value, SUM(value) FILTER(WHERE value > 100) OVER(PARTITION BY key ORDER BY value) AS total_value FROM mf_window_fun;
三、聚合统计类
| 函数 | 功能 |
|---|---|
SUM |
窗口求和 |
AVG |
窗口求平均值 |
COUNT |
窗口计数 |
MAX |
窗口最大值 |
MIN |
窗口最小值 |
MEDIAN |
窗口中位数 |
STDDEV |
窗口总体标准差 |
STDDEV_SAMP |
窗口样本标准差 |
1、定义
这一类与普通聚合函数的计算逻辑基本一致,但增加 OVER(...) 后不会压缩明细行。
2、区别
| 函数 | 核心区别 |
|---|---|
SUM |
总和 |
AVG |
平均值 |
COUNT |
数量 |
MAX |
最大值 |
MIN |
最小值 |
MEDIAN |
中位数 |
STDDEV |
总体标准差 |
STDDEV_SAMP |
样本标准差 |
最容易混淆:
| 对比 | 区别 |
|---|---|
普通 SUM vs 窗口 SUM |
前者通常减少行数;后者保留明细 |
AVG vs MEDIAN |
平均值 vs 中位数 |
STDDEV vs STDDEV_SAMP |
总体标准差 vs 样本标准差 |
3、简单记忆
SUM加起来。
AVG平均。
COUNT数数量。
MAX / MIN最大 / 最小。
MEDIAN找中间。
STDDEV总体标准差。
STDDEV_SAMP样本标准差。
4、案例
sql
-- 结果:每行显示所在部门的工资总额
SELECT deptno, ename, sal, SUM(sal) OVER(PARTITION BY deptno) AS dept_sum FROM emp;
-- 结果:每行显示所在部门的平均工资
SELECT deptno, ename, sal, AVG(sal) OVER(PARTITION BY deptno) AS dept_avg FROM emp;
-- 结果:每行显示所在部门的员工数量
SELECT deptno, ename, COUNT(*) OVER(PARTITION BY deptno) AS dept_cnt FROM emp;
-- 结果:每行显示所在部门的最高工资
SELECT deptno, ename, sal, MAX(sal) OVER(PARTITION BY deptno) AS dept_max FROM emp;
-- 结果:每行显示所在部门的最低工资
SELECT deptno, ename, sal, MIN(sal) OVER(PARTITION BY deptno) AS dept_min FROM emp;
-- 结果:每行显示所在部门的工资中位数
SELECT deptno, ename, sal, MEDIAN(sal) OVER(PARTITION BY deptno) AS dept_median FROM emp;
-- 结果:每行显示所在部门工资的总体标准差
SELECT deptno, ename, sal, STDDEV(sal) OVER(PARTITION BY deptno) AS dept_stddev FROM emp;
-- 结果:每行显示所在部门工资的样本标准差
SELECT deptno, ename, sal, STDDEV_SAMP(sal) OVER(PARTITION BY deptno) AS dept_stddev_samp FROM emp;
四、排名 / 分组类
| 函数 | 功能 |
|---|---|
ROW_NUMBER |
唯一连续编号 |
RANK |
并列排名,之后跳号 |
DENSE_RANK |
并列排名,之后不跳号 |
NTILE |
按顺序切成 N 组 |
1、定义
这一类都依赖 ORDER BY 的排序结果,对分区内数据进行编号、排名或分桶。
2、区别
假设排序结果为:
text
100
100
90
80
| 值 | ROW_NUMBER | RANK | DENSE_RANK |
|---|---|---|---|
| 100 | 1 | 1 | 1 |
| 100 | 2 | 1 | 1 |
| 90 | 3 | 3 | 2 |
| 80 | 4 | 4 | 3 |
| 函数 | 相同值处理 | 是否跳号 |
|---|---|---|
ROW_NUMBER |
不并列 | 不存在并列 |
RANK |
并列 | 跳号 |
DENSE_RANK |
并列 | 不跳号 |
NTILE |
不排名 | 切成 N 组 |
3、简单记忆
ROW_NUMBER= 每行唯一编号。
RANK= 并列后跳号。
DENSE_RANK= 排名很密,不跳号。
NTILE= 切成 N 份。
4、案例
sql
-- 结果:部门内工资从高到低生成唯一序号 1、2、3...
SELECT deptno, ename, sal, ROW_NUMBER() OVER(PARTITION BY deptno ORDER BY sal DESC) AS rn FROM emp;
-- 结果:相同工资并列,后续可能为 1、1、3
SELECT deptno, ename, sal, RANK() OVER(PARTITION BY deptno ORDER BY sal DESC) AS rk FROM emp;
-- 结果:相同工资并列,后续为 1、1、2
SELECT deptno, ename, sal, DENSE_RANK() OVER(PARTITION BY deptno ORDER BY sal DESC) AS dense_rk FROM emp;
-- 结果:部门内按工资从高到低切成 3 组
SELECT deptno, ename, sal, NTILE(3) OVER(PARTITION BY deptno ORDER BY sal DESC) AS salary_group FROM emp;
五、前后取值类
| 函数 | 功能 |
|---|---|
FIRST_VALUE |
当前窗口第一条值 |
LAST_VALUE |
当前窗口最后一条值 |
NTH_VALUE |
当前窗口第 N 条值 |
LAG |
当前行之前第 N 行 |
LEAD |
当前行之后第 N 行 |
1、定义
这一类根据窗口排序,从其他行获取对应字段值。
2、区别
| 函数 | 参照对象 |
|---|---|
FIRST_VALUE |
当前窗口第一行 |
LAST_VALUE |
当前窗口最后一行 |
NTH_VALUE |
当前窗口第 N 行 |
LAG |
相对当前行往前 |
LEAD |
相对当前行往后 |
核心区别:
FIRST_VALUE / LAST_VALUE / NTH_VALUE是看"窗口里的固定位置";
LAG / LEAD是看"当前行前后偏移"。
3、简单记忆
FIRST_VALUE第一条。
LAST_VALUE最后一条。
NTH_VALUE第 N 条。
LAG往前看。
LEAD往后看。
4、案例
sql
-- 结果:返回部门内按工资排序后的第一条工资
SELECT deptno, ename, sal, FIRST_VALUE(sal) OVER(PARTITION BY deptno ORDER BY sal) AS first_sal FROM emp;
-- 结果:返回部门内按工资排序后的最后一条工资
SELECT deptno, ename, sal, LAST_VALUE(sal) OVER(PARTITION BY deptno ORDER BY sal ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_sal FROM emp;
-- 结果:返回部门窗口中的第 2 条工资
SELECT deptno, ename, sal, NTH_VALUE(sal, 2) OVER(PARTITION BY deptno ORDER BY sal ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS second_sal FROM emp;
-- 结果:返回当前员工前一行工资,第一行返回 NULL
SELECT deptno, ename, sal, LAG(sal, 1) OVER(PARTITION BY deptno ORDER BY sal) AS prev_sal FROM emp;
-- 结果:返回当前员工后一行工资,最后一行返回 NULL
SELECT deptno, ename, sal, LEAD(sal, 1) OVER(PARTITION BY deptno ORDER BY sal) AS next_sal FROM emp;
六、分布 / 百分位类
| 函数 | 功能 |
|---|---|
CUME_DIST |
累计分布 |
PERCENT_RANK |
百分比排名 |
PERCENTILE_CONT |
连续型精确百分位 |
PERCENTILE_DISC |
离散型百分位 |
1、定义
这一类用于分析一条数据在整体分布中的位置,或计算 P50、P90、P95 等百分位。
2、区别
| 函数 | 核心含义 |
|---|---|
CUME_DIST |
当前值及之前累计占整体的比例 |
PERCENT_RANK |
当前排名在整体中的百分比位置 |
PERCENTILE_CONT |
连续型百分位,可通过插值产生不存在于原数据中的值 |
PERCENTILE_DISC |
离散型百分位,结果一定来自实际数据 |
例如数据:
text
1, 2, 3, 4
P50:
| 函数 | 结果特点 |
|---|---|
PERCENTILE_CONT |
可以得到 2.5 |
PERCENTILE_DISC |
返回实际存在的数据值 |
3、简单记忆
CUME_DIST= 累计占比。
PERCENT_RANK= 排名百分比。
CONT= Continuous,可以插值。
DISC= Discrete,只拿实际值。
4、案例
sql
-- 结果:返回每名员工工资的累计分布比例
SELECT deptno, ename, sal, CUME_DIST() OVER(PARTITION BY deptno ORDER BY sal) AS cume_dist FROM emp;
-- 结果:返回每名员工工资的百分比排名
SELECT deptno, ename, sal, PERCENT_RANK() OVER(PARTITION BY deptno ORDER BY sal DESC) AS pct_rank FROM emp;
-- 结果:返回部门工资精确 P50,可能产生插值
SELECT deptno, ename, sal, PERCENTILE_CONT(sal, 0.5) OVER(PARTITION BY deptno) AS p50 FROM emp;
-- 结果:返回部门工资离散 P50,结果来自实际工资值
SELECT deptno, ename, sal, PERCENTILE_DISC(sal, 0.5) OVER(PARTITION BY deptno) AS p50 FROM emp;
七、随机抽样类
| 函数 | 功能 |
|---|---|
CLUSTER_SAMPLE |
在窗口 / 分区内随机抽样 |
1、定义
CLUSTER_SAMPLE 用于对窗口内数据进行随机抽样,返回 BOOLEAN:
TRUE:当前行被抽中。FALSE:当前行未被抽中。
2、区别
它与排名后取前 N 条的最大区别:
| 对比 | 区别 |
|---|---|
ROW_NUMBER |
按明确排序规则确定性取数 |
CLUSTER_SAMPLE |
随机抽样 |
3、简单记忆
CLUSTER_SAMPLE= 分区内随机抽样。
4、案例
sql
-- 结果:每个部门随机抽样,被抽中的行返回 TRUE
SELECT deptno, ename, CLUSTER_SAMPLE(1) OVER(PARTITION BY deptno) AS is_sampled FROM emp;
八、最容易混淆的函数对比
| 函数组合 | 最核心区别 |
|---|---|
ROW_NUMBER vs RANK |
唯一编号 vs 相同值并列排名 |
RANK vs DENSE_RANK |
并列后跳号 vs 并列后不跳号 |
ROW_NUMBER vs NTILE |
每行编号 vs 将数据切成 N 组 |
LAG vs LEAD |
往前取值 vs 往后取值 |
FIRST_VALUE vs LAG |
窗口第一条 vs 当前行前 N 条 |
LAST_VALUE vs LEAD |
窗口最后一条 vs 当前行后 N 条 |
NTH_VALUE vs LAG / LEAD |
窗口固定位置 vs 相对当前行偏移 |
SUM OVER vs GROUP BY SUM |
保留明细行 vs 聚合减少行数 |
AVG vs MEDIAN |
平均值 vs 中位数 |
STDDEV vs STDDEV_SAMP |
总体标准差 vs 样本标准差 |
CUME_DIST vs PERCENT_RANK |
累计分布比例 vs 排名百分比 |
PERCENTILE_CONT vs PERCENTILE_DISC |
可插值 vs 只返回实际值 |
ROWS vs RANGE |
按行数范围 vs 按排序字段值范围 |
RANGE vs GROUPS |
按值范围 vs 按相同排序值组成的组 |
ROW_NUMBER vs CLUSTER_SAMPLE |
确定性排序编号 vs 随机抽样 |
九、常用案例
1、分组取最新一条
sql
-- 结果:每个 shop_id 只保留 etl_time 最新的一条
SELECT *
FROM (
SELECT t.*, ROW_NUMBER() OVER(PARTITION BY shop_id ORDER BY etl_time DESC) AS rn
FROM source_table t
) a
WHERE rn = 1;
2、按多个字段去重
sql
-- 结果:按 start_date、end_date、city 去重,每组保留 ds 最新的一条
SELECT *
FROM (
SELECT t.*, ROW_NUMBER() OVER(PARTITION BY TRIM(start_date), TRIM(end_date), TRIM(city) ORDER BY ds DESC) AS rn
FROM source_table t
) a
WHERE rn = 1;
3、累计求和
sql
-- 结果:每个门店按日期计算累计销售额
SELECT shop_id, biz_date, amount, SUM(amount) OVER(PARTITION BY shop_id ORDER BY biz_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS total_amount FROM sales;
4、计算环比
sql
-- 结果:获取前一天金额,并计算与当前金额差值
SELECT shop_id, biz_date, amount, LAG(amount, 1) OVER(PARTITION BY shop_id ORDER BY biz_date) AS prev_amount, amount - LAG(amount, 1) OVER(PARTITION BY shop_id ORDER BY biz_date) AS diff_amount FROM sales;
5、获取下一条记录
sql
-- 结果:获取同一门店的下一条业务日期
SELECT shop_id, biz_date, LEAD(biz_date, 1) OVER(PARTITION BY shop_id ORDER BY biz_date) AS next_date FROM sales;
6、分组 TOP N
sql
-- 结果:每个城市销售额最高的前 3 个门店
SELECT *
FROM (
SELECT city, shop_id, amount, ROW_NUMBER() OVER(PARTITION BY city ORDER BY amount DESC) AS rn
FROM shop_sales
) t
WHERE rn <= 3;
十、参考资料
阿里云 MaxCompute 官方文档: