各位亲爱的程序员朋友们,大家好!我是你们的老朋友,人称“Bug终结者”的程序猿老王。今天,咱们不聊那些深奥的算法,也不谈那些高冷的架构,咱们来聊点接地气,但又威力无穷的宝贝——窗口函数!
想象一下,你是一位古代的将军,手下千军万马。你想知道谁是跑得最快的士兵,谁的战斗力最强,或者想看看整个军团的战斗力趋势。如果你只能一个一个地比较,那得累死你!而窗口函数,就像你手中的一个魔法水晶球,能让你轻松洞察整个数据集的“全局”信息,同时还能关注到每个个体的“局部”特点。
今天,我们就一起探索窗口函数的“高级”应用,让你的SQL技能瞬间提升一个档次!准备好了吗?让我们扬帆起航,驶向窗口函数的星辰大海!🚀
一、 什么是窗口函数?(温故而知新)
在深入高级应用之前,我们先简单回顾一下窗口函数的基本概念。窗口函数,顾名思义,就是在某个“窗口”内进行计算的函数。这个“窗口”可以是整个表,也可以是按照某种规则划分的子集(分组)。
与传统的聚合函数(如SUM()
, AVG()
, COUNT()
)不同,窗口函数不会将多行数据聚合成一行,而是为每一行数据都返回一个计算结果。也就是说,窗口函数是在保留原始行的基础上,附加一些额外的信息。
举个例子,假设我们有一张销售表 sales
:
order_id | product | sales_amount | order_date |
---|---|---|---|
1 | A | 100 | 2023-01-01 |
2 | B | 150 | 2023-01-02 |
3 | A | 200 | 2023-01-03 |
4 | B | 120 | 2023-01-04 |
5 | A | 180 | 2023-01-05 |
如果我们想计算每天的销售额占总销售额的比例,用传统的聚合函数就比较麻烦,需要先计算总销售额,再进行连接查询。而使用窗口函数,一行代码就能搞定:
SELECT
order_id,
product,
sales_amount,
order_date,
sales_amount / SUM(sales_amount) OVER () AS sales_percentage
FROM
sales;
这里的 SUM(sales_amount) OVER ()
就是一个窗口函数。OVER ()
表示整个表都是一个窗口。它会计算整个表的销售总额,并为每一行数据都返回这个总额,然后我们就可以计算比例了。
看到这里,你是不是觉得窗口函数有点意思了?别急,精彩的还在后面!
二、 高级应用之一:分组排名 (Group Ranking)
分组排名,顾名思义,就是在每个分组内进行排名。这在很多场景下都非常有用,比如:
- 找出每个部门工资最高的员工: 你想知道哪个部门是“卷王”部门吗?🏆
- 统计每个地区销售额最高的商品: 看看哪个地区的消费者最“壕”!💰
- 对学生成绩进行分班排名: 谁是学霸,一目了然!🤓
窗口函数提供了几个排名函数:
RANK()
: 排名相同会跳过后续排名,例如:1, 1, 3DENSE_RANK()
: 排名相同不会跳过后续排名,例如:1, 1, 2ROW_NUMBER()
: 按照顺序依次排名,不会重复,例如:1, 2, 3NTILE(n)
: 将数据分成n组,并为每组分配一个组号。
我们还是以销售表 sales
为例,假设我们想找出每个产品销售额最高的订单:
SELECT
order_id,
product,
sales_amount,
order_date,
RANK() OVER (PARTITION BY product ORDER BY sales_amount DESC) AS product_rank
FROM
sales;
PARTITION BY product
: 按照product
列进行分组,这意味着每个产品都会有自己的排名。ORDER BY sales_amount DESC
: 按照sales_amount
列降序排序,销售额最高的排在前面。
运行结果如下:
order_id | product | sales_amount | order_date | product_rank |
---|---|---|---|---|
3 | A | 200 | 2023-01-03 | 1 |
5 | A | 180 | 2023-01-05 | 2 |
1 | A | 100 | 2023-01-01 | 3 |
2 | B | 150 | 2023-01-02 | 1 |
4 | B | 120 | 2023-01-04 | 2 |
我们可以看到,每个产品都按照销售额进行了排名。如果想只保留每个产品销售额最高的订单,可以将其作为子查询,然后筛选 product_rank = 1
的结果:
SELECT
order_id,
product,
sales_amount,
order_date
FROM
(
SELECT
order_id,
product,
sales_amount,
order_date,
RANK() OVER (PARTITION BY product ORDER BY sales_amount DESC) AS product_rank
FROM
sales
) AS ranked_sales
WHERE
product_rank = 1;
是不是很简单?分组排名,让你的数据分析更加精准!
三、 高级应用之二:累计求和 (Cumulative Sum)
累计求和,顾名思义,就是对某个数值列进行累计求和。这在很多场景下都非常有用,比如:
- 统计每日累计销售额: 了解销售额的增长趋势。📈
- 计算用户累计消费金额: 分析用户的消费习惯。 🛍️
- 跟踪项目累计完成进度: 掌握项目的整体进展。 🗓️
窗口函数提供了 SUM() OVER (ORDER BY ...)
来实现累计求和。
我们还是以销售表 sales
为例,假设我们想计算每日的累计销售额:
SELECT
order_date,
SUM(sales_amount) AS daily_sales,
SUM(sales_amount) OVER (ORDER BY order_date) AS cumulative_sales
FROM
sales
GROUP BY
order_date
ORDER BY
order_date;
SUM(sales_amount) OVER (ORDER BY order_date)
: 按照order_date
列进行排序,并计算累计销售额。
运行结果如下:
order_date | daily_sales | cumulative_sales |
---|---|---|
2023-01-01 | 100 | 100 |
2023-01-02 | 150 | 250 |
2023-01-03 | 200 | 450 |
2023-01-04 | 120 | 570 |
2023-01-05 | 180 | 750 |
我们可以看到,cumulative_sales
列显示了每日的累计销售额。通过这个数据,我们可以清晰地看到销售额的增长趋势。
如果想计算每个产品的累计销售额,只需要加上 PARTITION BY product
即可:
SELECT
order_date,
product,
sales_amount,
SUM(sales_amount) OVER (PARTITION BY product ORDER BY order_date) AS cumulative_sales
FROM
sales
ORDER BY
product,
order_date;
累计求和,让你的数据分析更加动态!
四、 高级应用之三:移动平均 (Moving Average)
移动平均,也称为滑动平均,是一种平滑时间序列数据的常用方法。它可以消除短期波动,突出长期趋势。这在很多场景下都非常有用,比如:
- 分析股票价格趋势: 过滤掉短期噪音,关注长期走向。 📈📉
- 预测网站流量: 预测未来的用户访问量。 🌐
- 监控传感器数据: 检测异常情况。 📡
窗口函数提供了 AVG() OVER (ORDER BY ... ROWS BETWEEN ...)
来实现移动平均。
我们还是以销售表 sales
为例,假设我们想计算过去3天的销售额移动平均值:
SELECT
order_date,
SUM(sales_amount) AS daily_sales,
AVG(SUM(sales_amount)) OVER (ORDER BY order_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_average
FROM
sales
GROUP BY
order_date
ORDER BY
order_date;
AVG(SUM(sales_amount)) OVER (ORDER BY order_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
: 按照order_date
列进行排序,并计算过去3天的销售额平均值。ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
指定了窗口的范围,表示从当前行往前数2行,到当前行。
运行结果如下:
order_date | daily_sales | moving_average |
---|---|---|
2023-01-01 | 100 | 100 |
2023-01-02 | 150 | 125 |
2023-01-03 | 200 | 150 |
2023-01-04 | 120 | 156.66666666666666 |
2023-01-05 | 180 | 166.66666666666666 |
我们可以看到,moving_average
列显示了过去3天的销售额移动平均值。通过这个数据,我们可以更清晰地看到销售额的长期趋势,而不会被每日的波动所干扰。
ROWS BETWEEN
子句可以灵活地定义窗口的范围,例如:
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
: 从第一行到当前行。ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
: 从当前行到最后一行。ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
: 当前行和前后各一行。
移动平均,让你的数据分析更加平滑!
五、 更多窗口函数 (More Window Functions)
除了上面介绍的排名函数、累计求和函数和移动平均函数之外,窗口函数还提供了很多其他有用的函数,比如:
FIRST_VALUE(column) OVER (...)
: 返回窗口中第一行的指定列的值。LAST_VALUE(column) OVER (...)
: 返回窗口中最后一行的指定列的值。LAG(column, n) OVER (...)
: 返回窗口中当前行之前第n行的指定列的值。LEAD(column, n) OVER (...)
: 返回窗口中当前行之后第n行的指定列的值。
这些函数可以帮助你更灵活地处理数据,满足各种各样的分析需求。
六、 窗口函数的注意事项 (Important Notes)
在使用窗口函数时,需要注意以下几点:
OVER()
子句是必须的: 窗口函数必须包含OVER()
子句,用于指定窗口的范围和排序方式。PARTITION BY
子句是可选的: 可以使用PARTITION BY
子句将数据分成多个分组,并在每个分组内进行计算。ORDER BY
子句是可选的: 可以使用ORDER BY
子句指定窗口内的排序方式。ROWS BETWEEN
子句是可选的: 可以使用ROWS BETWEEN
子句定义窗口的范围。- 性能问题: 窗口函数可能会影响查询性能,特别是当处理大数据集时。因此,需要谨慎使用,并进行性能测试。
七、 总结 (Conclusion)
窗口函数是SQL中一个非常强大的工具,它可以让你轻松地进行分组排名、累计求和、移动平均等高级数据分析操作。掌握窗口函数,可以极大地提高你的SQL技能,让你成为一名真正的数据分析专家!
希望今天的讲解对你有所帮助。记住,学习编程就像攀登一座高山,需要不断地学习和实践。不要害怕困难,勇往直前,你一定能到达成功的顶峰! ⛰️
最后,祝大家编码愉快,Bug少少! 👋
附录:练习题 (Practice Problems)
为了巩固你今天所学的知识,这里提供一些练习题,供你练习:
- 找出每个月销售额最高的商品。
- 计算每个用户的累计消费金额,并找出消费金额超过1000元的忠实用户。
- 计算过去7天的网站流量移动平均值,并预测未来3天的流量。
- 找出每个部门工资高于平均工资的员工。
希望这些练习题能帮助你更好地掌握窗口函数。加油!💪