mysql开窗函数
Posted 荼靡,
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql开窗函数相关的知识,希望对你有一定的参考价值。
mysql开窗函数
1.定义
- 窗口函数为了解决想要即显示聚集前的数据,又要显示聚集后的数据。
- 开窗函数对一组值进行操作,不需要group by 子句对数据进行分组,能够在同一行中同时返回基础行的列和聚合列
注意:开窗函数在mysql8.0后才有
2.语法
函数名(列) over(选项【选项可以为 partition by 列 order by 列】)
over(partition by xxx) 按照xxx所有行进行分组
over(partition by xxx order by aaa) 按照xxx分组,按照aaa排序
3.开窗函数的分类
3.1聚合开窗函数
- 函数名如果是聚合函数,则成为聚合开窗函数
- 语法:聚合函数(列) over(partition by 列 order by 行)
- 常见的聚合函数:sum(),count(),max(),min()…
计算每个学生的及格数
-- 使用聚合函数
select student_id,count(sid) from score where num>=60 group by student_id;
-- 使用开窗函数
select sid,student_id,count(sid) over(partition by student_id order by student_id) 及格数
from score where num>=60;
总结
开窗函数不会修改源数据表的结构,会在表的最后一列添加想要的结果,如果分组存在多行数据,则重复显示
3.2排序开窗函数
- row_number(行号)
- 生成连续的序号,不考虑分数相同
- rank(排名)
- 相同分数的排名一样,后面排名为真正的序号,排名存在跳跃性
- 例如:12225,排名为2的有3个,下一个排名为5,因为前面有4个成绩
- dense_rank(密集排序)
- 相同分数的排名一样,是连续的排名
- 例如:12223,排名为2的有3个,下一个排名为3
- ntile(分组排名)
- ntile有桶的概念
- ntile(6),将总记录划分为6桶,如果是12条记录,就是一桶2条记录
- 排名就是:112233445566
- 排序的结果数字大小只能用于桶与桶之间,桶内部虽然序号相同,但是num不一定相同
-- 看代码区分关系
select s.sid,s1.sname,s1.gender,c.cname,s.num,
row_number() over(partition by c.cname order by num desc) as row_number排名,
rank() over(partition by c.cname order by num desc) as rank排名,
dense_rank() over(partition by c.cname order by num desc) as dense_rank排名,
ntile(6) over(partition by c.cname order by num desc) as ntile排名
from score as s
join student s1 on s.student_id=s1.sid
left join course c on s.course_id=c.cid;
案例:
-- 查询各科成绩前三名的学生成绩信息
select * from(
select s.sid,s1.sname,s1.gender,c.cname,s.num,
dense_rank() over(partition by c.cname order by num desc) as dense_rank排名
from score as s
join student s1 on s.student_id=s1.sid
left join course c on s.course_id=c.cid) as e
where dense_rank排名<=3;
3.3其他
-
lag(col,n)
统计窗口内向上第n行值
-
lead(col,n)
统计窗口内往下第n行值
这两种函数可以用于同列中相邻行的数据相减操作
案例
-- 对于下面的数据,对于同一用户(uid)如果在2分钟之内重新登录,则判断为作弊,统计哪些用户有作弊行为,并计算作弊次数
-- 去时间差
SELECT *,
format(相差秒数 / 60, 3) 相差分钟数
FROM
( SELECT id,
uid,
login_time,
-- 加一列时间
lead (login_time, 1) over ( PARTITION BY uid ORDER BY login_time ) lead_time,
-- 求2列时间的差值
timestampdiff(
SECOND,
login_time,
( lead (login_time, 1) over ( PARTITION BY uid ORDER BY login_time ))
) 相差秒数
FROM lag_table ) as e;
-- 求作弊次数
SELECT uid,count(1) 作弊次数
FROM
( SELECT id,
uid,
login_time,
-- 加一列时间
lead (login_time, 1) over ( PARTITION BY uid ORDER BY login_time ) lead_time,
-- 求2列时间的差值
timestampdiff(
SECOND,
login_time,
( lead (login_time, 1) over ( PARTITION BY uid ORDER BY login_time ))
) 相差秒数
FROM lag_table ) as e
where format(相差秒数 / 60, 3)<=2 group by uid;
-
first_value(column)
取分组内排序后,截止当前行,第一个值
-- 取科目成绩最大值 -- 根据降序,第一个值是最大值 select s.sid,s1.sname,s1.gender,c.cname,s.num, first_value(num) over(partition by c.cname order by num desc) as firstvalue from score s join student s1 on s.student_id=s1.sid left join course c on s.course_id=c.cid;
-
last_value(column)
- 取分组内排序后,截止到当前行,最后一个值
- last_value()默认统计范围是rows between unbounded preceding and current row,就是取当前行数据与当前行之前的数据比较
修改范围: 在order by 后面加上rows between unbounded preceding and unbounded following 即:order by rows between unbounded preceding and unbounded following 就变为:当前分组数据与数据进行比较,取最后一个值 unbounded 无限制的 preceding 分区的当前记录向前的偏移量 current 当前 following 分区的当前记录向后偏移量
-- 取科目成绩最小值 -- 根据降序,最后一个值是最小值,要修改范围 select s.sid,s1.sname,s1.gender,c.cname,s.num, last_value(num) over(partition by c.cname order by num desc rows between unbounded preceding and unbounded following) as lastvalue from score s join student s1 on s.student_id=s1.sid left join course c on s.course_id=c.cid;
MySQL 开窗函数
什么是开窗函数
MySQL 在8.0版本以后才引入了开窗函数这个功能,而其他数据库 SQL Server Oracle 等 早已引用这个功能 也叫分析函数
开窗函数怎么用
语法:
开窗函数名([<字段名>]) over([partition by <分组字段>] [order by <排序字段> [desc]] [<窗口分区>])
栗子: first_value(amount) over(partition by user_id order by order time desc)
# first_value 这个函数是返回第一个值 这里就是 根据用户id分组 然后根据时间排序 返回满足条件的金额
开窗函数分类:
也可以分为如下几类
序号函数:row_number() / rank() / dense_rank()
分布函数:percent_rank() / cume_dist()
前后函数:lag() / lead()
头尾函数:first_value() / last_value()
其他函数:nth_value() / nfile()
使用场景
用户表和订单表,结构和数据如下:
drop table if EXISTS user_test;
CREATE table user_test (
id int PRIMARY key AUTO_INCREMENT,
user_account varchar(32),
user_name varchar(32),
user_password varchar(32),
user_age int(3)
);
drop table if EXISTS order_test;
CREATE table order_test (
order_id int PRIMARY key AUTO_INCREMENT,
user_id int not null ,
order_time datetime not null,
order_amount NUMERIC(11,2)
);
INSERT INTO `user_test` VALUES (1, 'test1', '张三', '123', 17);
INSERT INTO `user_test` VALUES (2, 'test2', '李四', '123', 18);
INSERT INTO `user_test` VALUES (3, 'test3', '王五', '123', 22);
INSERT INTO `order_test` VALUES (1, 1, '2021-06-18 17:00:57', 2200.00);
INSERT INTO `order_test` VALUES (2, 1, '2021-06-20 17:03:22', 1345.00);
INSERT INTO `order_test` VALUES (3, 3, '2021-06-23 20:04:05', 666.19);
查询需求: 查询累计消费金额大于1000块钱的用户信息
如果按照传统的写法 那么结果如下
SELECT
A.*,
B.sum_amount
FROM
user_test A
INNER JOIN ( SELECT user_id, sum( order_amount ) AS sum_amount FROM order_test GROUP BY user_id HAVING sum( order_amount )> 1000 ) AS B ON A.id = B.user_id
采用开窗函数方式来处理:
SELECT * from ( SELECT DISTINCT A.*,sum(B.order_amount) over(PARTITION by B.user_id) as sum_amount from
user_test A INNER JOIN order_test B
on A.id=B.user_id ) as T
WHERE sum_amount>1000
其实使用开窗函数的场景,不使用也能解决,如果是对某个字段进行排序然后,新增一列排序编号,那么用这个开窗函数很方便 比如这样
SELECT *,ROW_NUMBER()over(order by user_age desc ) as age_sort_no from user_test
以上是关于mysql开窗函数的主要内容,如果未能解决你的问题,请参考以下文章
小白学习MySQL - 增量统计SQL的需求 - 开窗函数的方案