MySQL 8.0 窗口函数:ROWS BETWEEN 与 RANGE BETWEEN 的爱恨情仇 (以及我们如何驾驭它们)
各位观众老爷们,晚上好!我是你们的老朋友,人称“代码界的段子手”的程序员老王。今天咱们不聊996,不谈内卷,来点轻松愉快的——MySQL 8.0 窗口函数中的 ROWS BETWEEN
和 RANGE BETWEEN
。
别看这俩名字挺唬人,其实就是让你在窗口函数里划定一个范围,告诉MySQL:嘿,哥们,我只要这个范围里的数据参与计算!就像你在年会上抽奖,总得先规定一个抽奖范围吧?(别告诉我你年年都抽到阳光普照奖,心疼你三秒… 😭)
一、窗口函数:数据库的“变形金刚”
首先,我们先简单回顾一下窗口函数。如果你已经对它了如指掌,可以直接跳到下一节。
窗口函数,顾名思义,就是在数据集中划出一个“窗口”,然后针对这个窗口里的数据进行计算。它和GROUP BY
很像,但是最大的区别在于:GROUP BY
会把数据聚合,导致行数变少;而窗口函数则不会,它会在每一行数据旁边,附带上窗口计算的结果,就像给每一行数据都穿上了“外挂”。
你可以把窗口函数想象成数据库的“变形金刚”,它可以根据你的需要,变形出各种各样的计算结果,而且还不会影响原始数据的结构。是不是很酷炫?😎
窗口函数的基本语法如下:
function(argument) OVER (
[PARTITION BY column1, column2, ...]
[ORDER BY column1 [ASC | DESC], column2 [ASC | DESC], ...]
[frame_clause]
)
function(argument)
:你想使用的窗口函数,比如SUM()
,AVG()
,ROW_NUMBER()
,RANK()
等等。OVER()
:这是窗口函数的灵魂!它告诉MySQL这是一个窗口函数,而不是普通的聚合函数。PARTITION BY column1, column2, ...
:可选,用于将数据集分成多个分区,每个分区就是一个独立的窗口。就像把一个班级分成几个小组,每个小组单独评奖。ORDER BY column1 [ASC | DESC], column2 [ASC | DESC], ...
:可选,用于指定窗口内数据的排序方式。就像小组内部,按照成绩排名。frame_clause
:可选,这就是我们今天的主角!用于定义窗口的范围,也就是我们说的ROWS BETWEEN
和RANGE BETWEEN
。
二、ROWS BETWEEN:按行数划定势力范围
ROWS BETWEEN
,顾名思义,就是按照行数来定义窗口的范围。它就像一个严谨的“数格子专家”,一丝不苟地按照行数来划定窗口范围。
ROWS BETWEEN
的语法如下:
ROWS BETWEEN frame_start AND frame_end
其中,frame_start
和 frame_end
可以是以下几种选项:
UNBOUNDED PRECEDING
:表示窗口从第一行开始。UNBOUNDED FOLLOWING
:表示窗口到最后一行结束。CURRENT ROW
:表示当前行。n PRECEDING
:表示当前行之前的n行。n FOLLOWING
:表示当前行之后的n行。
为了更好地理解,我们来看几个例子:
例1:计算每个员工的薪水,以及他和他前面一位员工的薪水总和。
假设我们有一个名为employees
的表,包含以下数据:
employee_id | employee_name | salary |
---|---|---|
1 | 张三 | 5000 |
2 | 李四 | 6000 |
3 | 王五 | 7000 |
4 | 赵六 | 8000 |
我们可以使用以下SQL语句来实现:
SELECT
employee_name,
salary,
SUM(salary) OVER (ORDER BY employee_id ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS salary_sum
FROM
employees;
执行结果如下:
employee_name | salary | salary_sum |
---|---|---|
张三 | 5000 | 5000 |
李四 | 6000 | 11000 |
王五 | 7000 | 13000 |
赵六 | 8000 | 15000 |
在这个例子中,ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
表示窗口范围为:当前行和当前行之前的1行。因此,对于李四来说,窗口范围就是张三和李四,薪水总和就是5000 + 6000 = 11000。
例2:计算每个员工的薪水,以及他和他后面一位员工的薪水总和。
SELECT
employee_name,
salary,
SUM(salary) OVER (ORDER BY employee_id ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) AS salary_sum
FROM
employees;
执行结果如下:
employee_name | salary | salary_sum |
---|---|---|
张三 | 5000 | 11000 |
李四 | 6000 | 13000 |
王五 | 7000 | 15000 |
赵六 | 8000 | 8000 |
在这个例子中,ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING
表示窗口范围为:当前行和当前行之后的1行。因此,对于王五来说,窗口范围就是王五和赵六,薪水总和就是7000 + 8000 = 15000。
总结:
ROWS BETWEEN
按照行数来划定窗口范围,简单粗暴,易于理解。- 适用于需要精确控制窗口大小的场景。
三、RANGE BETWEEN:按值划定势力范围
RANGE BETWEEN
则更加灵活,它是按照值来定义窗口的范围。它不像 ROWS BETWEEN
那样死板地数格子,而是根据指定的列的值,来动态地划定窗口范围。
RANGE BETWEEN
的语法如下:
RANGE BETWEEN frame_start AND frame_end
其中,frame_start
和 frame_end
可以是以下几种选项:
UNBOUNDED PRECEDING
:表示窗口从第一行开始,与ORDER BY
的列的值无关。UNBOUNDED FOLLOWING
:表示窗口到最后一行结束,与ORDER BY
的列的值无关。CURRENT ROW
:表示当前行,值与ORDER BY
的列的值相等的所有行。n PRECEDING
:表示值小于等于当前行ORDER BY
列的值减去 n 的所有行。n FOLLOWING
:表示值大于等于当前行ORDER BY
列的值加上 n 的所有行。
注意:
RANGE BETWEEN
必须和ORDER BY
子句一起使用。RANGE BETWEEN
只能用于数值类型或日期类型。
我们继续用例子来说明:
例1:计算每个员工的薪水,以及薪水差值在1000以内的员工的薪水总和。
还是用上面的employees
表。
SELECT
employee_name,
salary,
SUM(salary) OVER (ORDER BY salary RANGE BETWEEN 1000 PRECEDING AND 1000 FOLLOWING) AS salary_sum
FROM
employees;
执行结果如下:
employee_name | salary | salary_sum |
---|---|---|
张三 | 5000 | 11000 |
李四 | 6000 | 18000 |
王五 | 7000 | 21000 |
赵六 | 8000 | 15000 |
在这个例子中,RANGE BETWEEN 1000 PRECEDING AND 1000 FOLLOWING
表示窗口范围为:薪水小于等于当前员工薪水减去1000,且大于等于当前员工薪水加上1000的所有员工。
- 对于张三 (5000) 来说,窗口范围是 4000 到 6000,包括张三和李四,薪水总和是 5000 + 6000 = 11000。
- 对于李四 (6000) 来说,窗口范围是 5000 到 7000,包括张三、李四和王五,薪水总和是 5000 + 6000 + 7000 = 18000。
例2:统计每天的订单数量,以及前后两天(包括当天)的订单总数量。
假设我们有一个名为orders
的表,包含以下数据:
order_date | order_count |
---|---|
2023-10-26 | 10 |
2023-10-27 | 15 |
2023-10-28 | 20 |
2023-10-29 | 25 |
2023-10-30 | 30 |
我们可以使用以下SQL语句来实现:
SELECT
order_date,
order_count,
SUM(order_count) OVER (ORDER BY order_date RANGE BETWEEN INTERVAL 2 DAY PRECEDING AND INTERVAL 2 DAY FOLLOWING) AS total_order_count
FROM
orders;
执行结果如下:
order_date | order_count | total_order_count |
---|---|---|
2023-10-26 | 10 | 45 |
2023-10-27 | 15 | 70 |
2023-10-28 | 20 | 90 |
2023-10-29 | 25 | 90 |
2023-10-30 | 30 | 75 |
在这个例子中,RANGE BETWEEN INTERVAL 2 DAY PRECEDING AND INTERVAL 2 DAY FOLLOWING
表示窗口范围为:订单日期在当前日期之前2天(包括当天)和之后2天(包括当天)的所有订单。
总结:
RANGE BETWEEN
按照值来划定窗口范围,更加灵活,可以根据数据的特性来动态调整窗口大小。- 适用于需要根据值的范围进行计算的场景,例如统计某个时间段内的数据。
- 需要注意的是,
RANGE BETWEEN
必须和ORDER BY
子句一起使用,并且只能用于数值类型或日期类型。
四、ROWS BETWEEN vs RANGE BETWEEN:谁是你的菜?
现在,我们来总结一下 ROWS BETWEEN
和 RANGE BETWEEN
的区别:
特性 | ROWS BETWEEN | RANGE BETWEEN |
---|---|---|
定义范围 | 按照行数 | 按照值 |
适用场景 | 需要精确控制窗口大小的场景 | 需要根据值的范围进行计算的场景 |
是否需要 ORDER BY |
可选 | 必须 |
适用数据类型 | 所有数据类型 | 数值类型或日期类型 |
灵活性 | 较低 | 较高 |
那么,在实际应用中,我们应该选择哪一个呢?
- 如果你需要精确控制窗口的大小,并且不在乎数据的具体值,那么
ROWS BETWEEN
是你的不二之选。 就像你要统计一个班级里,每个学生的成绩和他前后两位同学的平均成绩,用ROWS BETWEEN
就很方便。 - 如果你需要根据数据的范围进行计算,并且希望窗口的大小能够动态调整,那么
RANGE BETWEEN
才是你的菜。 就像你要统计一段时间内的销售额,并且希望窗口的大小能够根据日期范围来动态调整,用RANGE BETWEEN
就更合适。
当然,具体选择哪一个,还是要根据你的实际需求来决定。就像选择女朋友一样,没有最好的,只有最适合你的。 (手动滑稽 😜)
五、实战演练:用案例说话
说了这么多理论,不如来几个实战案例,让大家更深入地理解 ROWS BETWEEN
和 RANGE BETWEEN
的用法。
案例1:计算每个用户的连续登录天数。
假设我们有一个名为user_login
的表,包含以下数据:
user_id | login_date |
---|---|
1 | 2023-10-26 |
1 | 2023-10-27 |
1 | 2023-10-28 |
1 | 2023-10-30 |
2 | 2023-10-27 |
2 | 2023-10-28 |
2 | 2023-10-29 |
我们可以使用以下SQL语句来计算每个用户的连续登录天数:
SELECT
user_id,
login_date,
COUNT(*) OVER (PARTITION BY user_id ORDER BY login_date RANGE BETWEEN INTERVAL '2' DAY PRECEDING AND CURRENT ROW) AS consecutive_days
FROM
user_login;
执行结果如下:
user_id | login_date | consecutive_days |
---|---|---|
1 | 2023-10-26 | 1 |
1 | 2023-10-27 | 2 |
1 | 2023-10-28 | 3 |
1 | 2023-10-30 | 1 |
2 | 2023-10-27 | 1 |
2 | 2023-10-28 | 2 |
2 | 2023-10-29 | 3 |
案例2:计算每个商品的移动平均价格。
假设我们有一个名为product_price
的表,包含以下数据:
product_id | price_date | price |
---|---|---|
1 | 2023-10-26 | 10 |
1 | 2023-10-27 | 12 |
1 | 2023-10-28 | 15 |
1 | 2023-10-29 | 13 |
1 | 2023-10-30 | 14 |
我们可以使用以下SQL语句来计算每个商品的3天移动平均价格:
SELECT
product_id,
price_date,
price,
AVG(price) OVER (PARTITION BY product_id ORDER BY price_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_average_price
FROM
product_price;
执行结果如下:
product_id | price_date | price | moving_average_price |
---|---|---|---|
1 | 2023-10-26 | 10 | 10 |
1 | 2023-10-27 | 12 | 11 |
1 | 2023-10-28 | 15 | 12.3333 |
1 | 2023-10-29 | 13 | 13.3333 |
1 | 2023-10-30 | 14 | 14 |
通过这两个案例,相信大家对 ROWS BETWEEN
和 RANGE BETWEEN
的用法有了更深入的理解。
六、总结:掌握窗口函数,解锁数据分析新姿势
今天,我们一起学习了 MySQL 8.0 窗口函数中的 ROWS BETWEEN
和 RANGE BETWEEN
,了解了它们的区别和适用场景,并通过实战案例加深了理解。
掌握了窗口函数,就相当于掌握了一把数据分析的瑞士军刀,可以轻松解决各种复杂的数据分析问题。
希望今天的分享能够帮助大家更好地理解和使用窗口函数,解锁数据分析的新姿势!💪
最后,别忘了点赞、评论、转发,让更多的小伙伴们受益!我们下期再见!👋