【SQL】SQL-常见窗口函数有哪些-上篇
- 一、引言
- 二、窗口函数基础概念
- 三、聚合类窗口函数(理解了,前面都是坦途!)
- 四、排名类窗口函数(面试有笔试的话,必考!)
-
- [1. ROW_NUMBER / RANK / DENSE_RANK 详解](#1. ROW_NUMBER / RANK / DENSE_RANK 详解)
- [2. 实战:班级内排名](#2. 实战:班级内排名)
- [3. 实战:筛选TopN](#3. 实战:筛选TopN)
- [4. 实战:连续出现N次 -- 【连续一出,面试基本能挂掉90%的人】](#4. 实战:连续出现N次 -- 【连续一出,面试基本能挂掉90%的人】)
- 五、总结对比
一、引言
今天来聊聊 Hive 里窗口函数的应用场景,原来大家对SQL这么感兴趣,阅读比Python高好多.orz
【窗口函数,绝对SQL进阶的关键门槛。SQL 水平能否上一个台阶,除了思维,更重要的是高效运用窗口函数。】
很多同学写聚合查询时只会 GROUP BY,其实窗口函数才是真爱啊,这个浮躁的社会。
窗口既能分组聚合,又不会丢失明细数据,还能轻松实现排名、累计、同比等高阶分析,妥妥的。
Hive 从 2.0 开始支持窗口函数,咱们先讲上篇------聚合类窗口函数 和 排名类窗口函数,配合实战解析,保证你看完就能用上。
- SQL专题往期内容:
二、窗口函数基础概念
在正式讲之前,先搞清楚几个关键概念,窗口函数的基础:
| 概念 | 说明 | 示例 |
|---|---|---|
| OVER() | 窗口函数的声明,定义计算窗口 | SUM(col) OVER(...) |
| PARTITION BY | 分区,类似 GROUP BY,但保留明细 | 按部门分组统计 |
| ORDER BY | 窗口内排序,决定计算顺序 | 按时间正序 |
| ROWS BETWEEN | 窗口边界,控制参与计算的行 | 往前3行~当前行 |
sql
-- 窗口函数基本语法
SELECT
SUM(column) OVER( PARTITION BY col1, col2 -- 可选:分区
ORDER BY col3 -- 可选:排序
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -- 可选:窗口边界
) AS 窗口函数基本语法
是不是看着很难理解?没事,确实也不简单,orz...慢慢来~
三、聚合类窗口函数(理解了,前面都是坦途!)
1. SUM 累计求和
基础用法
sql
-- 简单累计:所有数据求和
SELECT
name,
score,
SUM(score) OVER() AS 总分
FROM student_scores;
划重点:
OVER()不带任何参数时,窗口是全部数据,等同于GROUP BY的聚合效果,但保留每行明细。
带分区累计
sql
-- 按班级分组计算累计
SELECT
class,
name,
score,
SUM(score) OVER(PARTITION BY class) AS 班级总分,
ROUND(score * 100.0 / SUM(score) OVER(PARTITION BY class), 2) AS 班级占比
FROM student_scores;
带排序的滑动窗口
sql
-- 按时间顺序累计(滚动求和)
SELECT
dt,
sales,
SUM(sales) OVER(ORDER BY dt ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS 累计销售额
FROM daily_sales
ORDER BY dt;
划重点:
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW表示从第一行到当前行,这是滑动窗口求累计的标配写法。
2. AVG 平均值
sql
-- 计算每人的平均月度消费
SELECT
user_id,
month,
consumption,
AVG(consumption) OVER(PARTITION BY user_id ORDER BY month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ) AS 近3月平均 -- 包含当前月和前2个月
FROM user_consumption
ORDER BY user_id, month;
划重点:滑动窗口
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW实现了"近3个月平均"的效果,比GROUP BY灵活多了。
3. COUNT 计数
sql
-- 统计每笔订单之后还有多少笔订单 是不是比你们写一堆的LEFT JOIN 省力多了?
SELECT
order_id,
create_time,
amount,
COUNT(*) OVER( ORDER BY create_time ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS 后续订单数 -- 当前行之后所有行
FROM orders
ORDER BY create_time;
四、排名类窗口函数(面试有笔试的话,必考!)
1. ROW_NUMBER / RANK / DENSE_RANK 详解
这三个函数都是排名,但有区别:
| 函数 | 特点 | 示例(同样是1,2,2,3) |
|---|---|---|
| ROW_NUMBER | 连续不重复编号 | 1, 2, 3, 4 |
| RANK | 并列跳号 | 1, 2, 2, 4 |
| DENSE_RANK | 并列不跳号 | 1, 2, 2, 3 |
sql
-- 对比三种排名
SELECT
name,
score,
ROW_NUMBER() OVER(ORDER BY score DESC) AS 行号,
RANK() OVER(ORDER BY score DESC) AS 普通排名,
DENSE_RANK() OVER(ORDER BY score DESC) AS 紧凑排名
FROM student_scores
ORDER BY score DESC;
输出示例:
name | score | 行号 | 普通排名 | 紧凑排名
张三 | 95 | 1 | 1 | 1
李四 | 90 | 2 | 2 | 2
王五 | 90 | 3 | 2 | 2
赵六 | 85 | 4 | 4 | 3
划重点:分数相同时,
ROW_NUMBER给不同编号,RANK编号相同但下一个跳号,DENSE_RANK编号相同且下一个不跳号。
2. 实战:班级内排名
sql
-- 给每个班级的学生按成绩排名
SELECT
class,
name,
score,
ROW_NUMBER() OVER(PARTITION BY class ORDER BY score DESC) AS 班级排名,
RANK() OVER(PARTITION BY class ORDER BY score DESC) AS 班级排名_并列
FROM student_scores
ORDER BY class, 班级排名;
3. 实战:筛选TopN
sql
-- 取出每个班级成绩前3名
SELECT *
FROM (
SELECT
class,
name,
score,
ROW_NUMBER() OVER(PARTITION BY class ORDER BY score DESC) AS rn
FROM student_scores
) t
WHERE rn <= 3;
划重点:窗口函数不能直接在 WHERE 里用,得套一层子查询来实现筛选。
4. 实战:连续出现N次 -- 【连续一出,面试基本能挂掉90%的人】
sql
-- 找出连续3天以上活跃的用户
WITH user_active AS (
SELECT DISTINCT
user_id,
dt
FROM user_log
)
SELECT
user_id,
MIN(dt) AS 连续开始,
MAX(dt) AS 连续结束,
COUNT(*) AS 连续天数
FROM (
SELECT
user_id,
dt,
-- 把不连续的日期分组
dt - ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY dt) * 1 AS grp
FROM user_active
) t
GROUP BY user_id, grp
HAVING COUNT(*) >= 3
ORDER BY user_id, 连续开始;
这是经典的"连续天数"问题解法:通过
dt - rn把连续日期归到同一组。
五、总结对比
| 函数类别 | 常用函数 | 典型场景 |
|---|---|---|
| 聚合类 | SUM, AVG, COUNT, MAX, MIN | 累计、占比、滑动平均 |
| 排名类 | ROW_NUMBER, RANK, DENSE_RANK | 名次、TopN、连续天数 |
怎么样,窗口函数很好用吧~!关键是理解、属于数据结构中,如何构建结构的空间思维。
下篇预告: 偏移类窗口函数(LAG/LEAD/FIRST_VALUE/LAST_VALUE)以及综合实战案例,敬请期待!
今天的分享就到这里,觉得有用动动小手点赞+关注+收藏,一键三连~ 有问题留言沟通啦~