文章目录
- 一、窗口函数是什么
- 二、窗口函数的统一写法
- 三、最常见需求:每组取最新一条记录
- 四、窗口函数四大类
- [五、为什么 GROUP BY 不能直接查出整条最新记录?](#五、为什么 GROUP BY 不能直接查出整条最新记录?)
- 六、实用的几个模板
- 七、总结
在写 SQL 时都会遇到一个经典需求:
按某个字段分组(比如设备、用户、订单),取每组最新一条 / Top N 条记录
同时还想做:排名、累计统计、取上一条/下一条记录
但又不想把数据
GROUP BY之后"压扁"
这时就轮到 窗口函数(Window Function) 出场了。
一、窗口函数是什么
窗口函数 = 在"分组后的行集合"上做计算,但不合并行
它和 GROUP BY 最大的区别:
GROUP BY:把多行汇总成一行(明细没了)- 窗口函数:明细保留,但能在每一行上得到组内计算结果
二、窗口函数的统一写法
窗口函数都有这个结构:
sql
函数名(...) OVER (
PARTITION BY ...
ORDER BY ...
[ROWS / RANGE ...]
)
你可以理解为:
PARTITION BY:按什么分组(逻辑分组,不合并行)ORDER BY:组内按什么排序OVER:声明这是窗口函数
三、最常见需求:每组取最新一条记录
假设你有一张表:
resource_key:分组字段(比如设备ID/用户ID)createtime:时间字段
✅ 推荐写法(最稳)
sql
SELECT id, resource_key, longitude, latitude, createtime
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY resource_key
ORDER BY createtime DESC, id DESC
) AS rn
FROM track_data
) t
WHERE rn = 1;
为什么要加 id DESC?
因为实际数据里经常出现:
- 同一组内
- 同一时间戳
createtime - 有多条记录
只写 ORDER BY createtime DESC 时,"哪条排第一"可能不稳定。
加上 id DESC 就能保证:同时间戳时取 id 最大那条,结果稳定。
四、窗口函数四大类
① 排名类(最常用)
1️⃣ ROW_NUMBER(每行唯一编号)
sql
ROW_NUMBER() OVER (
PARTITION BY key
ORDER BY createtime DESC
)
特点:不管值是否相同,编号都递增:1、2、3...
用途:
- 每组最新 1 条(rn=1)
- 每组 Top N 条(rn<=N)
2️⃣ RANK(允许并列,会跳号)
sql
RANK() OVER (...)
例子:
| createtime | RANK |
|---|---|
| 10:00:05 | 1 |
| 10:00:05 | 1 |
| 10:00:03 | 3 |
用途:排名场景、允许并列名次
3️⃣ DENSE_RANK(允许并列,不跳号)
sql
DENSE_RANK() OVER (...)
用途:等级分层、分段统计
② 聚合类窗口函数(统计但不合并行)
COUNT / SUM / AVG / MAX / MIN OVER
sql
COUNT(*) OVER (PARTITION BY key) AS cnt
用途:
- 每行旁边展示"该组一共有多少条"
- 不需要
GROUP BY,明细仍保留
③ 偏移类(上一条/下一条)
⭐ LAG:上一条记录的值
sql
LAG(longitude) OVER (
PARTITION BY key
ORDER BY createtime
) AS prev_lng
用途:
- 取上一条数据做对比
- 计算差值(如金额差、数值变化)
LEAD:下一条记录的值
sql
LEAD(createtime) OVER (...) AS next_time
用途:
- 计算"本条到下一条"的间隔
- 分段分析
④ 首尾值函数(组内第一/最后)
FIRST_VALUE
sql
FIRST_VALUE(createtime) OVER (
PARTITION BY key
ORDER BY createtime DESC
) AS newest_time
LAST_VALUE(注意窗口范围)
默认容易踩坑,建议显式指定窗口范围:
sql
LAST_VALUE(createtime) OVER (
PARTITION BY key
ORDER BY createtime
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS last_time
五、为什么 GROUP BY 不能直接查出整条最新记录?
很多人会写:
sql
SELECT key, MAX(createtime)
FROM t
GROUP BY key;
这只能得到 每组最大时间 ,但拿不到这条时间对应的其它字段(经度、纬度等)。
你如果强行把其它字段也 select 出来:
sql
SELECT key, createtime, longitude
FROM t
GROUP BY key;
数据库会报错(标准 SQL 不允许),原因是:
同一个 key 有多行,longitude 有多个候选值,数据库不知道该选哪个
所以要么:
GROUP BY + MAX + JOIN(能拿回整行,但写法更绕)- 用窗口函数(更直观、可拓展 TopN)
六、实用的几个模板
1)每组 Top 3
sql
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY key ORDER BY createtime DESC) rn
FROM t
) x
WHERE rn <= 3;
2)每组统计条数 + 保留明细
sql
SELECT *,
COUNT(*) OVER (PARTITION BY key) AS group_cnt
FROM t;
3)对比上一条数据
sql
SELECT *,
LAG(value) OVER (PARTITION BY key ORDER BY createtime) AS prev_value
FROM t;
七、总结
ROW_NUMBER 只是窗口函数的入口
窗口函数真正强大之处在于:
既能做分组计算,又能保留每一行明细
常用记忆法:
- 取最新 / TopN :
ROW_NUMBER - 排名 :
RANK / DENSE_RANK - 统计但保留明细 :
COUNT/SUM/AVG OVER - 上一条/下一条 :
LAG / LEAD - 组内首尾 :
FIRST_VALUE / LAST_VALUE