MySQL从8.0开始支持窗口函数,有的也叫分析函数(处理相对复杂的报表统计分析场景),这个功能在大多商业数据库和部分开源数据库中早已支持。
窗口函数:窗口、函数(应用在窗口内的函数)-----窗口类似于窗户,限定一个空间范围
那什么叫窗口呢?
窗口的概念非常重要,它可以理解为记录集合,窗口函数也就是在满足某种条件的记录集合上执行的特殊函数。对于每条记录都要在此窗口内执行函数,窗口大小都是固定的,这种属于静态窗口;不同的记录对应着不同的窗口,这种动态变化的窗口叫滑动窗口。
窗口函数的基本用法如下:
函数名([expr]) over 子句
函数() over()
其中,over是关键字,用来指定函数执行的窗口范围,包含三个分析子句:分组(partition by)子句,排序(order by)子句,窗口(rows)子句,如果后面括号中什么都不写,则意味着窗口包含满足where条件的所有行,窗口函数基于所有行进行计算;如果不为空,则支持以下语法来设置窗口:
函数名([expr]) over(partition by <要分列的组> order by <要排序的列> rows
between <数据范围>)
sum(...A...) over(partition by ...B... order by ...C... rows between ...D1... and ...D2...)
avg(...A...) over(partition by ...B... order by ...C... rows between ...D1... and ...D2...)
A: 需要被加工的字段名称
B: 分组的字段名称
C: 排序的字段名称
D: 计算的行数范围
rows between 2 preceding and current row # 取当前行和前面两行
rows between unbounded preceding and current row # 包括本行和之前所有的行
rows between current row and unbounded following # 包括本行和之后所有的行
rows between 3 preceding and current row # 包括本行和前面三行
rows between 3 preceding and 1 following # 从前面三行和下面一行,总共五行
# 当order by后面缺少窗口从句条件,窗口规范默认是rows between unbounded preceding and current row.
# 当order by和窗口从句都缺失, 窗口规范默认是 rows between unbounded preceding and unbounded following
一般,我们可以把窗口函数分为两种:
专有窗口函数:
聚合类窗口函数:
普通场景下,聚合函数往往和group by一起使用,但是窗口环境下,聚合函数也可以应 用进来,那么此时它们就被称之为聚合类窗口函数,属于窗口函数的一种
窗口函数(专有窗口函数+聚合类窗口函数)和普通场景下的聚合函数也很容易混淆,二者区别如下:
现有2018~2020某电商平台订单信息表user_trade
表结构如下:
列名 | 释义 |
---|---|
user_name | 用户名 |
piece | 购买数量 |
price | 价格 |
pay_amount | 支付金额 |
goods_category | 商品品类 |
pay_time | 支付日期 |
# 建立数据表
use legou;
create table user_trade
(
user_name varchar(20),
piece int,
price double,
pay_amount double,
goods_category varchar(20),
pay_time date
);
查看前10行数据
------累计计算函数应用、排序函数应用、偏移分析函数应用
需求1: 查询出2019年每月的支付总额和当年累积支付总额
/*
需求1: 查询出2019年每月的支付总额和当年累积支付总额
*/
-- step1 过滤出2019年数据
select * from user_trade where year(pay_time)=2019;
-- step2 在1的基础上,按照月份进行group by 分组,统计每个月份的支付总额
SELECT
MONTH( pay_time ),
sum( pay_amount )
FROM
user_trade
WHERE
YEAR ( pay_time ) = 2019
GROUP BY
MONTH ( pay_time );
-- step3 在2的基础上应用窗口函数实现需求
SELECT
a.MONTH,-- 月份
a.pay_amount,-- 当月总支付金额
sum( a.pay_amount ) over ( ORDER BY a.MONTH ) -- 就是2019年的数据,所以
不用分组
-- --此时没有使用rows指定窗口数据范围,默认当前行及其之前的所有行
FROM
(
SELECT
MONTH( pay_time ) MONTH,
sum( pay_amount ) pay_amount
FROM
user_trade
WHERE
YEAR ( pay_time ) = 2019
GROUP BY
MONTH ( pay_time )
) a
结果如下:
需求2:查询出2018-2019年每月的支付总额和当年累积支付总额
SELECT a.year,
a.month,
a.pay_amount,
sum(a.pay_amount) over(partition by a.year order by a.month)
FROM
(SELECT year(pay_time) year,
month(pay_time) month,
sum(pay_amount) pay_amount
FROM user_trade
WHERE year(pay_time) in (2018,2019)
GROUP BY year(pay_time),
month(pay_time))a;
结果如下:
需求3: 查询出2019年每个月的近三月移动平均支付金额
SELECT a.month,
a.pay_amount,
avg(a.pay_amount) over(order by a.month rows between 2 preceding
and current row)
FROM
(SELECT month(pay_time) month,
sum(pay_amount) pay_amount
FROM user_trade
WHERE year(pay_time)=2019
GROUP BY month(pay_time))a;
结果如下:
需求4: 查询出每四个月的最大月总支付金额
SELECT a.month,
a.pay_amount,
max(a.pay_amount) over(order by a.month rows between 3 preceding
and current row)
FROM
(SELECT substr(pay_time,1,7) as month,
sum(pay_amount) as pay_amount
FROM user_trade
GROUP BY substr(pay_time,1,7))a;
结果如下;
需求5: 2020年1月,购买商品品类数的用户排名
/*
需求5: 2020年1月,购买商品品类数的用户排名
2020年1月(基础数据范围)
一个商品属于某一个品类
A用户购买了100件商品,那么可能涉及到了10个品类
B用户购买了50件商品,那么可能涉及到了15个品类
根据所购买商品涉及的品类数量进行排名(给用户)
思路:
1)先把各个用户所购买商品涉及的品类数给统计出来
2) 在1的基础上排名,使count()用到排名窗口函数
*/
-- 1)先把各个用户所购买商品涉及的品类数给统计出来
SELECT
user_name,
count( DISTINCT goods_category ) category_count,
row_number() over(order by count( DISTINCT goods_category ) ) order1,
-- row_number生成了行的编号从1开始
rank() over(order by count( DISTINCT goods_category ) ) order2,
dense_rank() over(order by count( DISTINCT goods_category ) ) order3
FROM
user_trade
WHERE
substring( pay_time, 1, 7 ) = '2020-01'
GROUP BY
user_name;
结果如下:
这三个函数的作用都是返回相应规则的排序序号
row_number() over(partition by ...A... order by ...B... )
rank() over(partition by ...A... order by ...B... )
dense_rank() over(partition by ...A... order by ...B... )
A:分组的字段名称
B:排序的字段名称
注意:
需求6: 查询出将2020年2月的支付用户,按照支付金额分成5组后的结果
SELECT user_name,
sum(pay_amount) pay_amount,
ntile(5) over(order by sum(pay_amount) desc) level
FROM user_trade
WHERE substr(pay_time,1,7)='2020-02'
GROUP BY user_name;
结果如下:
需求7: 查询出2020年支付金额排名前30%的所有用户
SELECT a.user_name,
a.pay_amount,
a.level
FROM
(SELECT user_name,
sum(pay_amount) pay_amount,
ntile(10) over(order by sum(pay_amount) desc) level
FROM user_trade
WHERE year(pay_time)=2020
GROUP BY user_name)a
WHERE a.level in (1,2,3);
结果如下:
ntile(n) over(partition by ...A... order by ...B... )
n:切分的片数
A:分组的字段名称
B:排序的字段名称
ntile(n),用于将分组数据按照顺序切分成n片,返回当前切片值 NTILE不支持ROWS BETWEEN。
需求8: 查询出King和West的时间偏移(前N行)
SELECT user_name,
pay_time,
lag(pay_time,1,pay_time) over(partition by user_name order by pay_time) lag1,
-- 没有传入偏移量,那么默认就是1,找不到的话,此处也没有给默认值,为null
lag(pay_time) over(partition by user_name order by pay_time) lag1_s,
lag(pay_time,2,pay_time) over(partition by user_name order by pay_time) lag2,
lag(pay_time,2) over(partition by user_name order by pay_time) lag2_s
FROM user_trade
WHERE user_name in ('King','West');
结果如下:
需求9: King和West的时间偏移(后N行)
SELECT user_name,
pay_time,
lead(pay_time,1,pay_time) over(partition by user_name order by pay_time) lead1,
lead(pay_time) over(partition by user_name order by pay_time) lead2,
lead(pay_time,2,pay_time) over(partition by user_name order by pay_time) lead3,
lead(pay_time,2) over(partition by user_name order by pay_time) lead4
FROM user_trade
WHERE user_name in ('King','West');
结果如下:
Lag和Lead函数可以在同一次查询中取出同一字段的前N行的数据(Lag)和后N行的数据(Lead) 作为独立的列。
在实际应用当中,若要用到取今天和昨天的某字段差值时,Lag和Lead函数的应用就显得尤为重要。 lag(exp_str,offset,defval) over(partion by ......order by ......)
lead(exp_str,offset,defval) over(partion by ......order by ......)
exp_str是字段名称。 offset是偏移量,即是上1个或上N个的值,假设当前行在表中排在第5行,则offset 为3,则表示我们所要找的数据行就是表中的第2行(即5-3=2)。offset默认值为1。defval默认值,当两个函数取上N/下N个值,当在表中从当前行位置向前数N行已经超出了表的范围时,lag()函数将defval这个参数值作为函数的返回值,若没有指定默认值,则返回NULL,那么在数学运算中,总要给一个默认值才不会出错。
需求10: 查询出支付时间间隔超过100天的用户数
SELECT count(distinct user_name)
FROM
(SELECT user_name,
pay_time,
lead(pay_time) over(partition by user_name order by pay_time) lead_dt
FROM user_trade)a
WHERE a.lead_dt is not null
and datediff(a.lead_dt,a.pay_time)>100;
结果如下:
需求11: 查询出每年支付时间间隔最长的用户
SELECT
years,
b.user_name,
b.pay_days
FROM
(SELECT
years,
a.user_name,
datediff(a.pay_time,a.lag_dt) pay_days,
rank() over(partition by years order by datediff(a.pay_time,a.lag_dt) desc) rank1
FROM
(SELECT
year(pay_time) as years,
user_name,
pay_time,
lag(pay_time) over(partition by user_name,year(pay_time) order by pay_time) lag_dt
FROM user_trade)a
WHERE a.lag_dt is not null)b
WHERE b.rank1=1;
结果如下:
发布于 2023-07-19 09:21・IP 属地上海