MySQL------窗口函数

一、窗口函数定义

1、 适用场景及分类
怎么样得到各部门工资排名前N名员工列表?
查找各部门每人工资占部门总工资的百分比?
累计求和如何计算?
连续统计N天登陆的用户?

以上类型问题均使用窗口函数可以解决,这类需求都有一个共同的特点,需要在单表中满足某些条件的结果集内部做一些函数操作,

对分组统计结果中的每一条记录进行计算的场景下, 使用窗口函数更好, 注意, 是每一条!! 因为MySQL的普通聚合函数的结果(如 group> by)是每一组只有一条记录!!!
窗口函数的作用类似于在查询中对数据进行分组,不同的是,分组操作会把分组的结果聚合成一条记录,而窗口函数是将分组的结果置于每一条数据记录中。

静态窗口函数的窗口大小是固定的, 不会因为记录的不同而不同;
动态窗口函数的窗口大小会随着记录的不同而变化;
窗口函数总体上可以分为序号函数, 分布函数, 前后函数, 首尾函数和其他函数;

2、语法
function over (partition by 字段a order by 字段b RANGE|ROWS BETWEEN start AND end)                

OVER 关键字指定窗口的范围;

如果省略后面括号中的内容,则窗口会包含满足WHERE条件的所有记录,窗口函数会基于所有满足WHERE条件的记录进行计算。

如果OVER关键字后面的括号不为空,则可以使用如下语法设置窗口:
PARTITION BY 子句: 指定窗口函数按照哪些字段进行分组,分组后, 窗口函数可以在每个分组中分别执行;
ORDER BY 子句: 指定窗口函数按照哪些字段进行排序,
执行排序操作使窗口函数按照排序后的数据记录的顺序进行编号;
FRAME 子句: 为分区中的某个子集定义规则, 可以用来作为滑动窗口使用;

窗口函数基于查询结果的行数据进行计算,窗口函数运行在HAVING子句之后、 ORDER BY子句之前。窗口函数需要特殊的关键字OVER子句来指定窗口即触发一个窗口函数。

二、函数分类

MySQL------窗口函数_第1张图片

1、序号函数
序号函数 功能
row_number() 顺序排序:对数据中的序号进行顺序显示,不管其排序结果是否出现重复值,排序结果为1,2,3,4,5…
rank() 并列排序:相同字段数值并列排序,且跳过重复序号,如, 1,1,3,4,5 。rank函数没有参数,但需要指定按照那个字段进行排名,所以使用rank函数必须用order by参数,order by的排序字段就是排名字段
dense_rank() 并列排序:相同字段数值并列排序,且不跳过重复序号,如:1,1,2,3,4

代码如下:

CREATE TABLE examination_info (
    id int PRIMARY KEY AUTO_INCREMENT COMMENT '自增ID',
    exam_id int UNIQUE NOT NULL COMMENT '试卷ID',
    tag varchar(32) COMMENT '类别标签',
    difficulty varchar(8) COMMENT '难度',
    duration int NOT NULL COMMENT '时长',
    release_time datetime COMMENT '发布时间'
)CHARACTER SET utf8 COLLATE utf8_general_ci;

CREATE TABLE user_info (
    id int PRIMARY KEY AUTO_INCREMENT COMMENT '自增ID',
    uid int UNIQUE NOT NULL COMMENT '用户ID',
    `nick_name` varchar(64) COMMENT '昵称',
    achievement int COMMENT '成就值',
    level int COMMENT '用户等级',
    job varchar(32) COMMENT '职业方向',
    register_time datetime COMMENT '注册时间'
)CHARACTER SET utf8 COLLATE utf8_general_ci;

CREATE TABLE exam_record (
    id int PRIMARY KEY AUTO_INCREMENT COMMENT '自增ID',
    uid int NOT NULL COMMENT '用户ID',
    exam_id int NOT NULL COMMENT '试卷ID',
    start_time datetime NOT NULL COMMENT '开始时间',
    submit_time datetime COMMENT '提交时间',
    score tinyint COMMENT '得分'
)CHARACTER SET utf8 COLLATE utf8_general_ci;

INSERT INTO user_info(uid,`nick_name`,achievement,`level`,job,register_time) VALUES
  (1001, '牛客1', 3200, 7, '算法', '2020-01-01 10:00:00'),
  (1002, '牛客2号', 2500, 6, '算法', '2020-01-01 10:00:00'),
  (1003, '牛客3号♂', 2200, 5, '算法', '2020-01-01 10:00:00');

INSERT INTO examination_info(exam_id,tag,difficulty,duration,release_time) VALUES
  (9001, 'SQL', 'hard', 60, '2020-01-01 10:00:00'),
  (9002, 'SQL', 'hard', 80, '2020-01-01 10:00:00'),
  (9003, '算法', 'hard', 80, '2020-01-01 10:00:00'),
  (9004, 'PYTHON', 'medium', 70, '2020-01-01 10:00:00');
	
INSERT INTO exam_record(uid,exam_id,start_time,submit_time,score) VALUES
(1001, 9001, '2020-01-01 09:01:01', '2020-01-01 09:21:59', 90),
(1002, 9001, '2020-01-20 10:01:01', null, null),
(1002, 9001, '2020-02-01 12:11:01', null, null),
(1003, 9001, '2020-03-01 19:01:01', null, null),
(1001, 9001, '2020-03-01 12:01:01', null, null),
(1002, 9001, '2020-03-01 12:01:01', '2020-03-01 12:41:01', 90),
(1002, 9001, '2020-05-02 19:01:01', '2020-05-02 19:32:00', 90),
(1001, 9002, '2020-01-02 19:01:01', '2020-01-02 19:59:01', 69),
(1001, 9002, '2020-02-02 12:01:01', '2020-02-02 12:20:01', 99),
(1002, 9002, '2020-02-02 12:01:01', null, null),
(1002, 9002, '2020-02-02 12:01:01', '2020-02-02 12:43:01', 81),
(1002, 9002, '2020-03-02 12:11:01', null, null),
(1001, 9001, '2020-01-02 10:01:01', '2020-01-02 10:31:01', 89),
(1001, 9002, '2020-01-01 12:11:01', null, null),
(1002, 9001, '2020-01-01 18:01:01', '2020-01-01 18:59:02', 90),
(1002, 9003, '2020-05-06 12:01:01', null, null),
(1001, 9002, '2020-05-05 18:01:01', null, null);

select nick_name,ei.exam_id,score,
row_number() over(partition by nick_name order by score desc) row_ranking,
rank() over(partition by nick_name order by score desc) ranking,
dense_rank() over(partition by nick_name order by score desc) dense_ranking
from user_info ui 
join exam_record er on ui.uid = er.uid
join examination_info ei on er.exam_id = ei.exam_id
where score is not null

2、分布函数
percent_rank() **累计百分比。**函数计算结果为:小于该条记录值的所有记录的行数/该分组的总行数-1,所以该记录的返回值为[0,1]。和之前的RANK()函数相关,每行按照如下公式进行计算: (rank - 1) / (rows - 1) 其中,rank为RANK()函数产生的序号,rows为当前窗口的记录总行数。
cume_dist() **累计分布值。**分组值小于等于当前值的行数与分组总行数的比值 ,(0,1]。 分组内大于等于当前rank值的行数/分组内总行数。(常用)

班级中比当前同学成绩高的学生比例是多少?

select nick_name,ei.exam_id,score,
PERCENT_RANK() OVER (PARTITION BY nick_name
			      ORDER BY score DESC) as percent,
CUME_DIST() OVER (PARTITION BY nick_name
                 ORDER BY score DESC) as cumdist
from user_info ui 
join exam_record er on ui.uid = er.uid
join examination_info ei on er.exam_id = ei.exam_id
where score is not null

3、前后函数

lead(字段,n) / lag(字段,n):分组中位于当前行后n行(lead)/ 前n行(lag)的记录值。
字段可以不填写,n(也叫offset)是从当前行偏移的行数,以获取值。offset必须是一个非负整数。如果offset为零,则LAG()函数计算当前行的值。如果省略 offset,则LAG()函数默认使用n=1, 向前看一个数据。

场景:求每个用户相邻两次浏览的时间差

求用户当前测试成绩的前一次测试成绩和后一次成绩

select nick_name,ei.exam_id,score,
 lead(score,1) OVER (PARTITION BY nick_name
                              ORDER BY score DESC) as leadVal,
 lag(score,1) OVER (PARTITION BY nick_name
                             ORDER BY score DESC) as lagVal
from user_info ui 
join exam_record er on ui.uid = er.uid
join examination_info ei on er.exam_id = ei.exam_id
where score is not null

4、头尾函数

first_val(expr) / last_val(expr):得到分区中的第一个/最后一个指定参数的值

select nick_name,ei.exam_id,score,
  FIRST_VALUE(score) OVER (PARTITION BY nick_name
                                   ORDER BY score DESC) as firstVal,
       LAST_VALUE(score) OVER (PARTITION BY nick_name
                                  ORDER BY score DESC) as lastVal
from user_info ui 
join exam_record er on ui.uid = er.uid
join examination_info ei on er.exam_id = ei.exam_id
where score is not null


5. 聚类窗口函数

聚和窗口函数和上面提到的专用窗口函数用法完全相同,只需要把聚合函数写在窗口函数的位置即可,但是函数后面括号里面不能为空,需要指定聚合的列名。
不改变表结构

函数 功能
min() 计算最小值
max() 计算最大值
count() 计数
sum() 求和
avg 求平均值
select *,
   sum(成绩) over (order by 学号) as current_sum,
   avg(成绩) over (order by 学号) as current_avg,
   count(成绩) over (order by 学号) as current_count,
   max(成绩) over (order by 学号) as current_max,
   min(成绩) over (order by 学号) as current_min
from 班级表

6、其他函数

nth_value(expr, n):返回窗口中第N个expr的值,expr可以是表达式,也可以是列名

排名第一和第二的分数

select nick_name,ei.exam_id,score,
  nth_value(score,1) OVER (PARTITION BY nick_name
                                   ORDER BY score DESC) as 1th,
       nth_value(score,2) OVER (PARTITION BY nick_name
                                   ORDER BY score DESC) as 2th
from user_info ui 
join exam_record er on ui.uid = er.uid
join examination_info ei on er.exam_id = ei.exam_id
where score is not null

nfile():将分区中的有序数据分为n个桶,记录桶号。
此函数在数据分析中应用较多,比如由于数据量大,需要将数据平均分配到N个并行的进程分别计算,此时就可以用NFILE(N)对数据进行分组,由于记录数不一定被N整除,所以数据不一定完全平均,多出来的部分则依次加给第一组、第二组···直到分配完。

你可能感兴趣的:(MySQL,mysql,数据库)