日常开发工作中经常会遇到如下需求:
- 去医院看病,怎么知道上次就医距现在的时长?
- 环比如何计算?
- 怎么得到各个部门工资篇排名前N名的员工列表?
- 如何查找组内人员没人工资占比总工资的百分比?
如果使用传统的SQL来解决,理论上是可以解决的,但逻辑却相当复杂。这类需求都有一个相同的特点,为了得到结果,都需要再在某个结果集内做一些特定的函数操作。 为了解决这一问题,MySQL8.0引入了窗口函数。
窗口的概念十分重要,它可以理解为记录合集,窗口函数也是满足某种条件的记录合集上执行特殊的函数,对于每条记录都要在此窗口内执行函数。有些函数随着记录不同,窗口大小都是固定的,这种属于静态窗口;有的函数则相反,不同的记录对应着不同的窗口,这种动态变化的窗口属于滑动窗口。
窗口函数和聚合函数有些类似,两者最大的区别是聚合函数是多行聚合为一行,窗口函数则是多行聚合为相同的行数,每行会多一个聚合后的新列。窗口函数在其他数据库(如Oracle)也被称为分析函数,功能也都大体相似。
MySQL中的窗口函数
函数 | 功能 |
---|---|
ROW_NUMBER() | 分区中当前的行号 |
RANK() | 当前行在分区中的排名,含序号间隙 |
DENSE_RANK() | 当前行在分区中的排名,不含序号间隙 |
PERCENT_RANK() | 百分比等级值 |
CUME_DIST() | 累计分配值 |
FIRST_VALUE() | 窗口中的第一行的参数值 |
LAST_VALUE() | 窗口中最后一行的参数值 |
LAG() | 分区中指定行落后于当前行的参数值 |
LEAG() | 分区中领先当前行的参数值 |
NTH_VALUE() | 从第N行窗口框架的参数值 |
NTILE() | 分区当前存储的行号 |
窗口函数在 MySQL 8.0.22 版本 测试
以订单表 order_tab 为例,逐个讲解这些函数的使用。测试表中的如下数据,各字段含义顺序 分别为: 订单号、用户 id、订单金额、创建日期:
mysql> SELECT * FROM order_tab;
+----------+---------+--------+-------------+
| order_id | user_no | amount | create_date |
+----------+---------+--------+-------------+
| 1 | 001 | 100 | 2021-01-01 |
| 2 | 001 | 300 | 2021-01-02 |
| 3 | 001 | 500 | 2021-01-02 |
| 4 | 001 | 800 | 2021-01-03 |
| 5 | 001 | 900 | 2021-01-04 |
| 6 | 002 | 500 | 2021-01-03 |
| 7 | 002 | 600 | 2021-01-04 |
| 8 | 002 | 300 | 2021-01-10 |
| 9 | 002 | 800 | 2021-01-16 |
| 10 | 002 | 800 | 2021-01-22 |
+----------+---------+--------+-------------+
10 rows in set (0.05 sec)
ROW_NUMBER 函数
如果要查询每个用户的最新一笔订单,我们希望的结果是 id为 5 和 10 的记录,此时我们可以使用 ROW_NUMBER()
函数按照用户进行分组并按照订单日期进行 降序排序,最后查找每组中 序列为 1 的数据:
mysql> SELECT * FROM
-> (
-> SELECT ROW_NUMBER() OVER(partition by user_no order by create_date desc) as row_num,
-> order_id,user_no,amount,create_date
-> FROM order_tab
-> ) t WHERE row_num = 1;
+---------+----------+---------+--------+-------------+
| row_num | order_id | user_no | amount | create_date |
+---------+----------+---------+--------+-------------+
| 1 | 5 | 001 | 900 | 2021-01-04 |
| 1 | 10 | 002 | 800 | 2021-01-22 |
+---------+----------+---------+--------+-------------+
2 rows in set (0.44 sec)
其中,row_number()
后面的 over
是关键字,用来指定函数执行的窗口范围,如果后面的括号中什么都写,则意味着窗口包含所有行,窗口函数在所有行上进行计算;如果不为空,则支持以下四种写法:
window_name
给窗口指定一个别名,如果 SQL 中涉及的窗口较多,采用别名则更清晰易读,上面的例子中如果指定一个别名 w,则代码改写如下:SELECT * FROM ( SELECT row_number()over w AS row_num,order_id,user_no FROM order_tab WINDOW w as (partition BY user_no ORDER BY create_date DESC ) ) t WHERE row_num = 1
partition
子句:窗口按照哪些字段进行分组, 窗口函数在不同的分组上分别执行,上面的例子就是按照用户id 分组。在每一个用户的消费记录上 分别从1 开始顺序编号。order by
子句:按照哪些字段进行排序, 窗口函数将按照排序后的顺序进行编号,既可以和 parition 子句配合使用,也可以单独使用。frame
子句:frame 是当前分区(分组后的数据,一组为一个分区)的一个子集,子句可以定义子集的规则,通常用来作为滑动窗口使用。例如要根据每个订单动态计算包括本订单按时间顺序前后两个订单的平均订单金额,则可以通过设置 frame 子句来创建滑动窗口:mysql> SELECT * FROM ( select order_id,user_no,amount, avg(amount) over w avg_num, create_date FROM order_tab WINDOW w AS (partition by user_no order by create_date DESC ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) ) t; +----------+---------+--------+----------+-------------+ | order_id | user_no | amount | avg_num | create_date | +----------+---------+--------+----------+-------------+ | 5 | 001 | 900 | 850.0000 | 2021-01-04 | | 4 | 001 | 800 | 666.6667 | 2021-01-03 | | 2 | 001 | 300 | 533.3333 | 2021-01-02 | | 3 | 001 | 500 | 300.0000 | 2021-01-02 | | 1 | 001 | 100 | 300.0000 | 2021-01-01 | | 10 | 002 | 800 | 800.0000 | 2021-01-22 | | 9 | 002 | 800 | 633.3333 | 2021-01-16 | | 8 | 002 | 300 | 566.6667 | 2021-01-10 | | 7 | 002 | 600 | 466.6667 | 2021-01-04 | | 6 | 002 | 500 | 550.0000 | 2021-01-03 | +----------+---------+--------+----------+-------------+ 10 rows in set (0.02 sec)
order_id 为 5的订单数据边界值,没有前面的一行,因此平均金额为 "(900+800) / 2 = 850";order_id 为4 的订单前后都有订单,所以订单平均金额为 "(900+800+300) / 3 = 666.6667",以此类推就可以得到一个基于滑动窗口的动态订单平均值。
对于滑动窗口的范围指定,有如下两种方式。
基于行:通常使用
BETWEEN frame_start AND frame_end
语法来表示行范围,frame_start 和 frame_end 可以支持如下关键字,来确定不同的动态行记录:CURRENT ROW
边界行是当前行,一般和其他关键字一起使用。UNBOUNDED PRECEDING
边界是分区中的第一行UNBOUNDED FOLLOWING
边界是分区中的最后一行expr PRECEDING
边界是当前行减去 expr 的值expr FOLLOWING
边界是当前行加上 expr 的值- 比如下面都是合法的范围:
-- 窗口范围是 当前行、前 1 行、后 1 行,共 3 行记录 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING -- 窗口范围是当前行到分区中的最后一行 ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING -- 窗口的范围是当前分区中的所有行,等同于不写 (书中这么说,但发现不完全是) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
- 如果 不写范围 但写了排序 则会按照排序顺序进行合集计算:
mysql> SELECT * FROM ( SELECT order_id, user_no, amount, sum(amount) over ( PARTITION BY user_no) sum_num1, sum(amount) over w sum_num2, create_date FROM order_tab WINDOW w AS ( PARTITION BY user_no ORDER BY create_date DESC) ) t; +----------+---------+--------+----------+----------+-------------+ | order_id | user_no | amount | sum_num1 | sum_num2 | create_date | +----------+---------+--------+----------+----------+-------------+ | 5 | 001 | 900 | 2600 | 900 | 2021-01-04 | | 4 | 001 | 800 | 2600 | 1700 | 2021-01-03 | | 2 | 001 | 300 | 2600 | 2500 | 2021-01-02 | | 3 | 001 | 500 | 2600 | 2500 | 2021-01-02 | | 1 | 001 | 100 | 2600 | 2600 | 2021-01-01 | | 10 | 002 | 800 | 3000 | 800 | 2021-01-22 | | 9 | 002 | 800 | 3000 | 1600 | 2021-01-16 | | 8 | 002 | 300 | 3000 | 1900 | 2021-01-10 | | 7 | 002 | 600 | 3000 | 2500 | 2021-01-04 | | 6 | 002 | 500 | 3000 | 3000 | 2021-01-03 | +----------+---------+--------+----------+----------+-------------+ 10 rows in set (0.03 sec)
- 可以看到
sum_num1
是分区内所有数据的合集,而sum_num2
是按照排序叠加的合集
- 基于范围: 和基础行类似,但有些范围不是直接可以用行数来表示的,比如窗口范围是一周前的订单开始,截止到当前行,则无法使用 rows 来直接表示,此时可以使用范围来表示窗口:
INTERVAL 7 DAY PRECEDING
。
RANK / DENSE_RANK 函数
RANK()
和 DENSE_RANK()
函数与 ROW_NUMBER()
函数非常相似,只是在出现重复值时处理逻辑有所不同。
假设需要查询不同用户的订单,按照订单金额进行排序,SQL 语句中使用 row_number()、rank()、dense_rank() 分别显示序号,看一下有什么区别:
mysql> SELECT * FROM
-> (
-> SELECT row_number() over(partition by user_no order by amount desc) as row_num1,
-> rank() over(partition by user_no order by amount desc) as row_num2,
-> dense_rank() over(partition by user_no order by amount desc) as row_num3,
-> order_id,user_no,amount,create_date
-> FROM order_tab
-> ) t;
+----------+----------+----------+----------+---------+--------+-------------+
| row_num1 | row_num2 | row_num3 | order_id | user_no | amount | create_date |
+----------+----------+----------+----------+---------+--------+-------------+
| 1 | 1 | 1 | 5 | 001 | 900 | 2021-01-04 |
| 2 | 2 | 2 | 4 | 001 | 800 | 2021-01-03 |
| 3 | 3 | 3 | 3 | 001 | 500 | 2021-01-02 |
| 4 | 4 | 4 | 2 | 001 | 300 | 2021-01-02 |
| 5 | 5 | 5 | 1 | 001 | 100 | 2021-01-01 |
| 1 | 1 | 1 | 9 | 002 | 800 | 2021-01-16 |
| 2 | 1 | 1 | 10 | 002 | 800 | 2021-01-22 |
| 3 | 3 | 2 | 7 | 002 | 600 | 2021-01-04 |
| 4 | 4 | 3 | 6 | 002 | 500 | 2021-01-03 |
| 5 | 5 | 4 | 8 | 002 | 300 | 2021-01-10 |
+----------+----------+----------+----------+---------+--------+-------------+
10 rows in set (0.00 sec)
row_number()
在金额都是 800 的两条记录上随机排序,但序号按照1、2递增,后面金额为 600 的记录 继续递增为 3,序号之间不会产生间隙。rank()
将两条金额都是 800 的记录序号设置为 1,后续的金额 为 600 的记录则 设置为 3,即即生成序号相同的记录,同时可能会产生间隙。dense_rank()
将两条金额都是 800 的记录序号设置为 1,后续的金额 为 600 的记录则 设置为 2, 即生成序号相同的记录,但不会产生间隙。
PERCENT_RANK / CUME_DIST 函数
PERCENT_RANK()
和 CUME_DIST()
这两个函数都是计算数据分布的函数。
PERCENT_RANK()
PERCENT_RANK()
函数和之前的 RANK()
函数相关,每行按照下面的公式计算:
-- rank 为 RANK() 函数产生的序号
-- rows 为 当前窗口的记录总行数
(rank - 1) / (rows - 1)
使用上面的例子 修改如下:
mysql> select * from
-> (
-> select
-> rank() over w row_num,
-> percent_rank() over w percent,
-> order_id,user_no,amount,create_date
-> from order_tab
-> WINDOW w AS (partition by user_no order by amount desc)
-> ) t;
+---------+---------+----------+---------+--------+-------------+
| row_num | percent | order_id | user_no | amount | create_date |
+---------+---------+----------+---------+--------+-------------+
| 1 | 0 | 5 | 001 | 900 | 2021-01-04 |
| 2 | 0.25 | 4 | 001 | 800 | 2021-01-03 |
| 3 | 0.5 | 3 | 001 | 500 | 2021-01-02 |
| 4 | 0.75 | 2 | 001 | 300 | 2021-01-02 |
| 5 | 1 | 1 | 001 | 100 | 2021-01-01 |
| 1 | 0 | 9 | 002 | 800 | 2021-01-16 |
| 1 | 0 | 10 | 002 | 800 | 2021-01-22 |
| 3 | 0.5 | 7 | 002 | 600 | 2021-01-04 |
| 4 | 0.75 | 6 | 002 | 500 | 2021-01-03 |
| 5 | 1 | 8 | 002 | 300 | 2021-01-10 |
+---------+---------+----------+---------+--------+-------------+
10 rows in set (0.10 sec)
可以看到 percent 列按照公式代入了rank 和 rows 的值(user_no 为 001 和 002 的值均为 5),此函数主要用于分析领域,日常使用不多。
CUME_DIST()
相比 percent_rank() ,cume_dist() 的应用场景更多,它的作用是:分组内小于 当前 rank 值的行数/分区内的总行数。
在下面的代码中,统计大于等于当前订单金额的订单数,站总订单的比例:
mysql> SELECT * FROM
-> (
-> SELECT
-> rank() over w row_num,
-> cume_dist() over w cume,
-> order_id,user_no,amount,create_date
-> FROM order_tab
-> WINDOW w AS (partition by user_no order by amount desc)
-> ) t;
+---------+------+----------+---------+--------+-------------+
| row_num | cume | order_id | user_no | amount | create_date |
+---------+------+----------+---------+--------+-------------+
| 1 | 0.2 | 5 | 001 | 900 | 2021-01-04 |
| 2 | 0.4 | 4 | 001 | 800 | 2021-01-03 |
| 3 | 0.6 | 3 | 001 | 500 | 2021-01-02 |
| 4 | 0.8 | 2 | 001 | 300 | 2021-01-02 |
| 5 | 1 | 1 | 001 | 100 | 2021-01-01 |
| 1 | 0.4 | 9 | 002 | 800 | 2021-01-16 |
| 1 | 0.4 | 10 | 002 | 800 | 2021-01-22 |
| 3 | 0.6 | 7 | 002 | 600 | 2021-01-04 |
| 4 | 0.8 | 6 | 002 | 500 | 2021-01-03 |
| 5 | 1 | 8 | 002 | 300 | 2021-01-10 |
+---------+------+----------+---------+--------+-------------+
10 rows in set (0.00 sec)
NEILE 函数
NEILE(N)
函数的功能是对数据分区中的有序结果集进行划分,将其分为N个组,每一组分配一个唯一的编号。
针对上面的例子,将数据分为3组:
mysql> SELECT * FROM
-> (
-> SELECT
-> ntile(3) over w as nf,
-> order_id,user_no,amount,create_date
-> FROM order_tab
-> WINDOW w AS (partition by user_no order by amount desc)
-> ) t;
+------+----------+---------+--------+-------------+
| nf | order_id | user_no | amount | create_date |
+------+----------+---------+--------+-------------+
| 1 | 5 | 001 | 900 | 2021-01-04 |
| 1 | 4 | 001 | 800 | 2021-01-03 |
| 2 | 3 | 001 | 500 | 2021-01-02 |
| 2 | 2 | 001 | 300 | 2021-01-02 |
| 3 | 1 | 001 | 100 | 2021-01-01 |
| 1 | 9 | 002 | 800 | 2021-01-16 |
| 1 | 10 | 002 | 800 | 2021-01-22 |
| 2 | 7 | 002 | 600 | 2021-01-04 |
| 2 | 6 | 002 | 500 | 2021-01-03 |
| 3 | 8 | 002 | 300 | 2021-01-10 |
+------+----------+---------+--------+-------------+
10 rows in set (0.00 sec)
此函数在数据分析中用的比较多,比如由于数据量大,需要将数据分配到N个进程中分别计算,此时就可以使用 NFILE(N)
对数据进行分组,由于记录的数据不一定被N整除,所以每组的记录数量不一定完全一致,然后将不同组号的数据再分配。
NTH_VALUE 函数
NTH_VALUE(expr,N)
函数可以返回第 N 个 expr 的值,也可以是列名:
mysql> SELECT * FROM
-> (
-> SELECT
-> ntile(3) over w as nf,
-> nth_value(order_id,3) over w as nth,
-> order_id,user_no,amount,create_date
-> FROM order_tab
-> WINDOW w as (partition by user_no order by amount desc)
-> )t;
+------+------+----------+---------+--------+-------------+
| nf | nth | order_id | user_no | amount | create_date |
+------+------+----------+---------+--------+-------------+
| 1 | NULL | 5 | 001 | 900 | 2021-01-04 |
| 1 | NULL | 4 | 001 | 800 | 2021-01-03 |
| 2 | 3 | 3 | 001 | 500 | 2021-01-02 |
| 2 | 3 | 2 | 001 | 300 | 2021-01-02 |
| 3 | 3 | 1 | 001 | 100 | 2021-01-01 |
| 1 | NULL | 9 | 002 | 800 | 2021-01-16 |
| 1 | NULL | 10 | 002 | 800 | 2021-01-22 |
| 2 | 7 | 7 | 002 | 600 | 2021-01-04 |
| 2 | 7 | 6 | 002 | 500 | 2021-01-03 |
| 3 | 7 | 8 | 002 | 300 | 2021-01-10 |
+------+------+----------+---------+--------+-------------+
10 rows in set (0.11 sec)
nth 列返回分组排序后的窗口中的 order_id 的第三个值,"001" 用户返回 3,"002" 用户返回7,而 对于前N-1的列,本函NULL。
经测试 如 返回的列的 rank() 值与 当前分组其他 rank() 值 一样时(或者说处于同一个滑动窗口中),则同时修改前面一个的nth:
(order_id 为 9 和10 的数据 rank() 的值都是 1,所以 9 的 nth 也会返回 10
mysql> SELECT * FROM
(
SELECT
rank() over w num,
ntile(3) over w as nf,
nth_value(order_id,2) over w as nth,
order_id,user_no,amount,create_date
FROM order_tab
WINDOW w as (partition by user_no order by amount desc)
)t;
+-----+----+------+----------+---------+--------+-------------+
| num | nf | nth | order_id | user_no | amount | create_date |
+-----+----+------+----------+---------+--------+-------------+
| 1 | 1 | NULL | 5 | 001 | 900 | 2021-01-04 |
| 2 | 1 | 4 | 4 | 001 | 800 | 2021-01-03 |
| 3 | 2 | 4 | 3 | 001 | 500 | 2021-01-02 |
| 4 | 2 | 4 | 2 | 001 | 300 | 2021-01-02 |
| 5 | 3 | 4 | 1 | 001 | 100 | 2021-01-01 |
| 1 | 1 | 10 | 9 | 002 | 800 | 2021-01-16 |
| 1 | 1 | 10 | 10 | 002 | 800 | 2021-01-22 |
| 3 | 2 | 10 | 7 | 002 | 600 | 2021-01-04 |
| 4 | 2 | 10 | 6 | 002 | 500 | 2021-01-03 |
| 5 | 3 | 10 | 8 | 002 | 300 | 2021-01-10 |
+-----+----+------+----------+---------+--------+-------------+
10 rows in set (0.04 sec)
LAG / LEAD 函数
LAG(expr,N)
和 LEAD(expr,N)
这两个函数的功能是获取当前行按照某种排序规则的上 N 行(LAG) 和下 N 行(LEAD)数据的某个字段。例如需要获取本订单距离上一个订单相隔几天:
mysql> SELECT t.*,DATEDIFF(create_date,last_date) as diffday FROM
(
SELECT
order_id,user_no,amount,create_date,
LAG(create_date,1) over w as last_date
FROM order_tab
WINDOW w as (partition by user_no order by create_date)
) t;
+----------+---------+--------+-------------+------------+---------+
| order_id | user_no | amount | create_date | last_date | diffday |
+----------+---------+--------+-------------+------------+---------+
| 1 | 001 | 100 | 2021-01-01 | NULL | NULL |
| 2 | 001 | 300 | 2021-01-02 | 2021-01-01 | 1 |
| 3 | 001 | 500 | 2021-01-02 | 2021-01-02 | 0 |
| 4 | 001 | 800 | 2021-01-03 | 2021-01-02 | 1 |
| 5 | 001 | 900 | 2021-01-04 | 2021-01-03 | 1 |
| 6 | 002 | 500 | 2021-01-03 | NULL | NULL |
| 7 | 002 | 600 | 2021-01-04 | 2021-01-03 | 1 |
| 8 | 002 | 300 | 2021-01-10 | 2021-01-04 | 6 |
| 9 | 002 | 800 | 2021-01-16 | 2021-01-10 | 6 |
| 10 | 002 | 800 | 2021-01-22 | 2021-01-16 | 6 |
+----------+---------+--------+-------------+------------+---------+
10 rows in set (0.05 sec)
FIRST_VALUE / LAST_VALUE 函数
FIRST_VALUE(expr)
函数和 LAST_VALUE(expr)
函数的功能分别是获取滑动窗口中参数字段的第一个(FIRST_VALUE)和最后一个( LAST_VALUE )的值。
下面的例子中,每个用户在每个订单记录中希望看到啊截至当前订单为止,按照日期排序最早的订单和最晚订单的订单金额:
mysql> SELECT * FROM
-> (
-> SELECT
-> order_id,user_no,amount,create_date,
-> FIRST_VALUE(amount) over w as first_amount,
-> LAST_VALUE(amount) over w as last_amount
-> FROM order_tab
-> WINDOW w as (partition by user_no order by create_date)
-> ) t;
+----------+---------+--------+-------------+--------------+-------------+
| order_id | user_no | amount | create_date | first_amount | last_amount |
+----------+---------+--------+-------------+--------------+-------------+
| 1 | 001 | 100 | 2021-01-01 | 100 | 100 |
| 2 | 001 | 300 | 2021-01-02 | 100 | 500 |
| 3 | 001 | 500 | 2021-01-02 | 100 | 500 |
| 4 | 001 | 800 | 2021-01-03 | 100 | 800 |
| 5 | 001 | 900 | 2021-01-04 | 100 | 900 |
| 6 | 002 | 500 | 2021-01-03 | 500 | 500 |
| 7 | 002 | 600 | 2021-01-04 | 500 | 600 |
| 8 | 002 | 300 | 2021-01-10 | 500 | 300 |
| 9 | 002 | 800 | 2021-01-16 | 500 | 800 |
| 10 | 002 | 800 | 2021-01-22 | 500 | 800 |
+----------+---------+--------+-------------+--------------+-------------+
10 rows in set (0.00 sec)
聚合函数作为窗口函数
除了前面说的窗口函数外,聚合函数也可以作为窗口函数来使用,比如要统计每个用户截止到当前订单的累计订单金额、平均订单金额、最大订单金额、订单数等,可以使用聚合函数作为窗口函数来使用:
mysql> SELECT * FROM
-> (
-> SELECT
-> order_id,user_no,amount,create_date,
-> sum(amount) over w as sum1,
-> avg(amount) over w as avg1,
-> max(amount) over w as max1,
-> min(amount) over w as min1,
-> count(amount) over w as count1
-> FROM order_tab
-> WINDOW w as (partition by user_no order by create_date)
-> ) t;
+----------+---------+--------+-------------+------+----------+------+------+--------+
| order_id | user_no | amount | create_date | sum1 | avg1 | max1 | min1 | count1 |
+----------+---------+--------+-------------+------+----------+------+------+--------+
| 1 | 001 | 100 | 2021-01-01 | 100 | 100.0000 | 100 | 100 | 1 |
| 2 | 001 | 300 | 2021-01-02 | 900 | 300.0000 | 500 | 100 | 3 |
| 3 | 001 | 500 | 2021-01-02 | 900 | 300.0000 | 500 | 100 | 3 |
| 4 | 001 | 800 | 2021-01-03 | 1700 | 425.0000 | 800 | 100 | 4 |
| 5 | 001 | 900 | 2021-01-04 | 2600 | 520.0000 | 900 | 100 | 5 |
| 6 | 002 | 500 | 2021-01-03 | 500 | 500.0000 | 500 | 500 | 1 |
| 7 | 002 | 600 | 2021-01-04 | 1100 | 550.0000 | 600 | 500 | 2 |
| 8 | 002 | 300 | 2021-01-10 | 1400 | 466.6667 | 600 | 300 | 3 |
| 9 | 002 | 800 | 2021-01-16 | 2200 | 550.0000 | 800 | 300 | 4 |
| 10 | 002 | 800 | 2021-01-22 | 3000 | 600.0000 | 800 | 300 | 5 |
+----------+---------+--------+-------------+------+----------+------+------+--------+
10 rows in set (0.00 sec)
笔记来自:《深入浅出MySQL:数据库开发,优化与管理维护(第三版)》