在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
前三名和我预料大差不差,分别是: 1、游标2、窗口函数3、聚簇索引 这三个点虽然平时用得少,但在 今天我想和你聊聊窗口函数, 好了,废话不多说,老规矩,先上开胃小菜,看看今天的测试表数据吧。 本文用来演示用的测试表是 mysql> SELECT * from chh_baozipu ; +----+--------------------+-------+---------+ | id | product | sales | month | +----+--------------------+-------+---------+ | 1 | 猪肉大葱包子 | 600 | 2021-11 | | 2 | 猪肉大葱包子 | 1600 | 2021-10 | | 3 | 猪肉大葱包子 | 1000 | 2021-09 | | 4 | 猪肉大葱包子 | 800 | 2021-08 | | 5 | 猪肉大葱包子 | 1600 | 2021-07 | | 6 | 猪肉大葱包子 | 1000 | 2021-06 | | 7 | 面馅儿包子 | 700 | 2021-11 | | 8 | 面馅儿包子 | 200 | 2021-10 | | 9 | 面馅儿包子 | 300 | 2021-09 | | 10 | 面馅儿包子 | 0 | 2021-08 | | 11 | 面馅儿包子 | 100 | 2021-07 | | 12 | 面馅儿包子 | 200 | 2021-06 | +----+--------------------+-------+---------+ 12 rows in set (0.00 sec) 怎么说?什么时候来我店里,请大家吃面馅儿包子。 一、什么是窗口函数1、怎么理解窗口? 其实 拿测试表举个简单的例子,统计一下: SELECT *,SUM(sales) over(ORDER BY `month`) as 累计利润 from chh_baozipu where product='猪肉大葱包子'; mysql> SELECT *,SUM(sales) over(ORDER BY `month`) as 累计利润 from chh_baozipu where product='猪肉大葱包子'; +----+--------------------+-------+---------+--------------+ | id | product | sales | month | 累计利润 | +----+--------------------+-------+---------+--------------+ | 6 | 猪肉大葱包子 | 1000 | 2021-06 | 1000 | | 5 | 猪肉大葱包子 | 1600 | 2021-07 | 2600 | | 4 | 猪肉大葱包子 | 800 | 2021-08 | 3400 | | 3 | 猪肉大葱包子 | 1000 | 2021-09 | 4400 | | 2 | 猪肉大葱包子 | 1600 | 2021-10 | 6000 | | 1 | 猪肉大葱包子 | 600 | 2021-11 | 6600 | +----+--------------------+-------+---------+--------------+ 6 rows in set (0.00 sec) 从这条SQL可以看出,对于第一行id=6这行的 可见, 对于每条记录都要在此窗口内执行函数,有的函数随着记录不同,窗口大小都是固定的,这种属于 2、什么是窗口函数
窗口函数多用在什么场景?主要有以下两类:
我们常见的窗口函数和聚合函数有这些:
因为聚合函数也可以放在窗口函数中使用,因此窗口函数和普通聚合函数也很容易被混淆,二者区别如下:
二、窗口函数用法基本语法: <窗口函数> OVER (PARTITION BY <用于分组的列名> ORDER BY <用于排序的列名>); -- over关键字用于指定函数的窗口范围, -- partition by 用于对表分组, -- order by子句用于对分组后的结果进行排序。
窗口函数都有哪些?懒得画了,借
让我们来分别举例看一看: 1、序号函数:row_number() / rank() / dense_rank()ROW_NUMBER():顺序排序 —— 1、2、3 RANK():并列排序,跳过重复序号 —— 1、1、3 DENSE_RANK():并列排序,不跳过重复序号 —— 1、1、2 mysql> SELECT *,ROW_NUMBER() over(ORDER BY sales desc) as pro_ROW_NUMBER,rank() over(ORDER BY sales desc) as pro_rank,DENSE_RANK() over(ORDER BY sales desc) as pro_DENSE_RANK from chh_baozipu where product='猪肉大葱包子'; +----+--------------------+-------+---------+----------------+----------+----------------+ | id | product | sales | month | pro_ROW_NUMBER | pro_rank | pro_DENSE_RANK | +----+--------------------+-------+---------+----------------+----------+----------------+ | 2 | 猪肉大葱包子 | 1600 | 2021-10 | 1 | 1 | 1 | | 5 | 猪肉大葱包子 | 1600 | 2021-07 | 2 | 1 | 1 | | 3 | 猪肉大葱包子 | 1000 | 2021-09 | 3 | 3 | 2 | | 6 | 猪肉大葱包子 | 1000 | 2021-06 | 4 | 3 | 2 | | 4 | 猪肉大葱包子 | 800 | 2021-08 | 5 | 5 | 3 | | 1 | 猪肉大葱包子 | 600 | 2021-11 | 6 | 6 | 4 | +----+--------------------+-------+---------+----------------+----------+----------------+ 6 rows in set (0.00 sec) 如上述示例可见,三个窗口函数服务与不同的三个典型业务需求,这三种足以应对我们的排序统计。 以后同学们在面试或笔试时被问到时,请不要再说自查询嵌套之类的lowB方案了,不然可别说你认识我~狗子们 2、分布函数:percent_rank() / cume_dist()这个分布函数基本不用,不讲。有兴趣的同学自行百度~ 3、前后函数:lag(expr,n) / lead(expr,n)
前后函数常用于:返回位于当前行的 应用场景:查询前n名同学的成绩和当前同学成绩的差值 内层SQL先通过LAG()函数得到前1名同学的成绩,外层SQL再将当前同学和前1名同学的成绩做差得到成绩差值diff。 这里换成哈哥的测试表就有点尬了。。但你肯定明白这意思,来,让我们尬查一下: mysql> SELECT *,lag(sales,1) over win as pro_lag,lead(sales,1) over win as pro_lead from chh_baozipu WINDOW win as (PARTITION BY product ORDER BY sales desc); +----+--------------------+-------+---------+---------+----------+ | id | product | sales | month | pro_lag | pro_lead | +----+--------------------+-------+---------+---------+----------+ | 2 | 猪肉大葱包子 | 1600 | 2021-10 | NULL | 1600 | | 5 | 猪肉大葱包子 | 1600 | 2021-07 | 1600 | 1000 | | 3 | 猪肉大葱包子 | 1000 | 2021-09 | 1600 | 1000 | | 6 | 猪肉大葱包子 | 1000 | 2021-06 | 1000 | 800 | | 4 | 猪肉大葱包子 | 800 | 2021-08 | 1000 | 600 | | 1 | 猪肉大葱包子 | 600 | 2021-11 | 800 | NULL | | 7 | 面馅儿包子 | 700 | 2021-11 | NULL | 300 | | 9 | 面馅儿包子 | 300 | 2021-09 | 700 | 200 | | 8 | 面馅儿包子 | 200 | 2021-10 | 300 | 200 | | 12 | 面馅儿包子 | 200 | 2021-06 | 200 | 100 | | 11 | 面馅儿包子 | 100 | 2021-07 | 200 | 0 | | 10 | 面馅儿包子 | 0 | 2021-08 | 100 | NULL | +----+--------------------+-------+---------+---------+----------+ 12 rows in set (0.00 sec) 这里我想问一下同学们是不是发现这条SQL和前面SQL不同?有哪几个地方不同呢? SELECT *, lag(sales,1) over win as pro_lag, lead(sales,1) over win as pro_lead from chh_baozipu where product='猪肉大葱包子' WINDOW win as (PARTITION BY product ORDER BY sales desc); 1、 其实,这种是把窗口提了出来, 有人问程序员要什么简洁?别人看不懂才会觉得代码牛B啊。这种同学一看就是没被社会毒打过,等你遇到百年一见的祖传代码时候,你就懂啥叫大道至简了(借胖哥图一用)。 2、窗口中增加了 这个关键字在over子句中,也就意味着控制了窗口的内容,在上面基础语法中我告诉你over中有两个个关键词:
其实,还有更有意思的控制窗口范围的方式~~ 对于 通常使用BETWEEN frame_start AND frame_end语法来表示行范围,frame_start和frame_end可以支持如下关键字,来确定不同的动态行记录:
来看几个例子: ①计算当前行与前n行(共n+1行)的聚合窗口函数 下例中控制窗口大小为当前月+前两个月的利润总和,来看一下效果: SELECT *,SUM(sales) OVER win as '近三个月利润相加' FROM chh_baozipu WINDOW win as (PARTITION BY product ORDER BY `month` ROWS 2 PRECEDING); mysql> SELECT *,SUM(sales) OVER win as '近三个月利润相加' -> FROM chh_baozipu -> WINDOW win as (PARTITION BY product ORDER BY `month` ROWS 2 PRECEDING); +----+--------------------+-------+---------+--------------------------+ | id | product | sales | month | 近三个月利润相加 | +----+--------------------+-------+---------+--------------------------+ | 6 | 猪肉大葱包子 | 1000 | 2021-06 | 1000 | | 5 | 猪肉大葱包子 | 1600 | 2021-07 | 2600 | | 4 | 猪肉大葱包子 | 800 | 2021-08 | 3400 | | 3 | 猪肉大葱包子 | 1000 | 2021-09 | 3400 | | 2 | 猪肉大葱包子 | 1600 | 2021-10 | 3400 | | 1 | 猪肉大葱包子 | 600 | 2021-11 | 3200 | | 12 | 面馅儿包子 | 200 | 2021-06 | 200 | | 11 | 面馅儿包子 | 100 | 2021-07 | 300 | | 10 | 面馅儿包子 | 0 | 2021-08 | 300 | | 9 | 面馅儿包子 | 300 | 2021-09 | 400 | | 8 | 面馅儿包子 | 200 | 2021-10 | 500 | | 7 | 面馅儿包子 | 700 | 2021-11 | 1200 | +----+--------------------+-------+---------+--------------------------+ 12 rows in set (0.00 sec) ②计算当前行与前n1行、后n2行的聚合窗口函数 下例中控制窗口大小为当前月前一个月到后一个月的利润总和,来看一下效果: SELECT *,SUM(sales) OVER win as '前三个月利润相加' FROM chh_baozipu WINDOW win as (PARTITION BY product ORDER BY `month` ROWS BETWEEN n1 PRECEDING AND n2 FOLLOWING); mysql> SELECT *,SUM(sales) OVER win as '前一个月到下一个月利润相加' FROM chh_baozipu WINDOW win as (PARTITION BY product ORDER BY `month` ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING); +----+--------------------+-------+---------+--------------------------+ | id | product | sales | month |前一个月到下一个月利润相加| +----+--------------------+-------+---------+--------------------------+ | 6 | 猪肉大葱包子 | 1000 | 2021-06 | 2600 | | 5 | 猪肉大葱包子 | 1600 | 2021-07 | 3400 | | 4 | 猪肉大葱包子 | 800 | 2021-08 | 3400 | | 3 | 猪肉大葱包子 | 1000 | 2021-09 | 3400 | | 2 | 猪肉大葱包子 | 1600 | 2021-10 | 3200 | | 1 | 猪肉大葱包子 | 600 | 2021-11 | 2200 | | 12 | 面馅儿包子 | 200 | 2021-06 | 300 | | 11 | 面馅儿包子 | 100 | 2021-07 | 300 | | 10 | 面馅儿包子 | 0 | 2021-08 | 400 | | 9 | 面馅儿包子 | 300 | 2021-09 | 500 | | 8 | 面馅儿包子 | 200 | 2021-10 | 1200 | | 7 | 面馅儿包子 | 700 | 2021-11 | 900 | +----+--------------------+-------+---------+--------------------------+ 12 rows in set (0.00 sec) 4、头尾函数:FIRST_VALUE(expr)、LAST_VALUE(expr)头尾函数应用于:返回第一个或最后一个expr的值; 应用场景:截止到当前,按照日期排序查询 SELECT *, FIRST_VALUE(sales) over win as '当前最大月收入', LAST_VALUE(sales) over win as '当前最小月收入' from chh_baozipu WINDOW win as (PARTITION BY product ORDER BY `month`); mysql> SELECT *,FIRST_VALUE(sales) over win as '当前最大月收入',LAST_VALUE(sales) over win as '当前最小月收入' from chh_baozipu WINDOW win as (PARTITION BY product ORDER BY `month`); +----+--------------------+-------+---------+-----------------------+-----------------------+ | id | product | sales | month | 当前最大月收入 | 当前最小月收入 | +----+--------------------+-------+---------+-----------------------+-----------------------+ | 6 | 猪肉大葱包子 | 1000 | 2021-06 | 1000 | 1000 | | 5 | 猪肉大葱包子 | 1600 | 2021-07 | 1000 | 1600 | | 4 | 猪肉大葱包子 | 800 | 2021-08 | 1000 | 800 | | 3 | 猪肉大葱包子 | 1000 | 2021-09 | 1000 | 1000 | | 2 | 猪肉大葱包子 | 1600 | 2021-10 | 1000 | 1600 | | 1 | 猪肉大葱包子 | 600 | 2021-11 | 1000 | 600 | | 12 | 面馅儿包子 | 200 | 2021-06 | 200 | 200 | | 11 | 面馅儿包子 | 100 | 2021-07 | 200 | 100 | | 10 | 面馅儿包子 | 0 | 2021-08 | 200 | 0 | | 9 | 面馅儿包子 | 300 | 2021-09 | 200 | 300 | | 8 | 面馅儿包子 | 200 | 2021-10 | 200 | 200 | | 7 | 面馅儿包子 | 700 | 2021-11 | 200 | 700 | +----+--------------------+-------+---------+-----------------------+-----------------------+ 12 rows in set (0.00 sec) 5、其他函数:nth_value() / nfile()nfile()不常用,不再赘述;这里我们只提一下 NTH_VALUE用途:返回窗口中第n个expr的值。 应用场景:截止到当前,显示陈哈哈包子铺月利润榜中排名第2和第3的成绩的利润。 SELECT *, nth_value(sales,2) over win as '当前排名第二的月收入', nth_value(sales,3) over win as '当前排名第三的月收入' from chh_baozipu WINDOW win as (PARTITION BY product ORDER BY `month`); mysql> SELECT *,nth_value(sales,2) over win as '当前排名第二的月收入',nth_value(sales,3) over win as '当前排名第三的月收入' from chh_baozipu WINDOW win as (PARTITION BY product ORDER BY `month`); +----+--------------------+-------+---------+--------------------------------+--------------------------------+ | id | product | sales | month | 当前排名第二的月收入 | 当前排名第三的月收入 | +----+--------------------+-------+---------+--------------------------------+--------------------------------+ | 6 | 猪肉大葱包子 | 1000 | 2021-06 | NULL | NULL | | 5 | 猪肉大葱包子 | 1600 | 2021-07 | 1600 | NULL | | 4 | 猪肉大葱包子 | 800 | 2021-08 | 1600 | 800 | | 3 | 猪肉大葱包子 | 1000 | 2021-09 | 1600 | 800 | | 2 | 猪肉大葱包子 | 1600 | 2021-10 | 1600 | 800 | | 1 | 猪肉大葱包子 | 600 | 2021-11 | 1600 | 800 | | 12 | 面馅儿包子 | 200 | 2021-06 | NULL | NULL | | 11 | 面馅儿包子 | 100 | 2021-07 | 100 | NULL | | 10 | 面馅儿包子 | 0 | 2021-08 | 100 | 0 | | 9 | 面馅儿包子 | 300 | 2021-09 | 100 | 0 | | 8 | 面馅儿包子 | 200 | 2021-10 | 100 | 0 | | 7 | 面馅儿包子 | 700 | 2021-11 | 100 | 0 | +----+--------------------+-------+---------+--------------------------------+--------------------------------+ 12 rows in set (0.00 sec) 本章小结窗口函数就说到这里,窗口函数是我接触MySQL8以后发现的新东西,突然感觉MySQL开发团队还是很灵性的,每个版本都会新增一些玩儿法,当然也很实用,希望MySQL9.0会给我们带来更多的惊喜。 到此这篇关于MySQL窗口函数的具体使用的文章就介绍到这了,更多相关MySQL窗口函数内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论