背景

自mysql8.0之后支持了开窗函数,开窗函数主要解决了,明细数据列表增加一列展示聚合后的值,如分数score表,有所有学生的分数,在我们想看每位学生分数的同时还想查看这门课的平均分,或者最大分,或者最小分,没有开窗函数之前解决起来比较麻烦,有了开窗函数之后可以很方便的解决如下:

select 
    student_name, -- 学生姓名
    score,avg(score) over(partition by class_id) -- 根据课程id分组后取分数平均值
from score_table -- 分数表

 常用开窗函数

除了和max,min,avg,count,sum搭配之外,还支持和以下函数组合:

分类函数作用
序号函数ROW_NUMBER()(添加序号)会为结果集中的每一行分配一个唯一的连续整数序号,不会有重复的序号。当有相同排序值时,每行都会有不同的序号。
RANK()(排名)会为结果集中的每一行分配一个排名,如果有相同的排序值,则会跳过相同的排名,下一个排名会按照跳过的数量递增。
DENSE_RANK()(排名)也会为结果集中的每一行分配一个排名,但不会跳过相同的排名,相同的排序值会有相同的排名,排名是连续的。
分布函数PERCENT_RANK()用于计算某一行在结果集中的相对排名百分比。它返回一个介于0和1之间的值,表示当前行在整个结果集中的相对位置。(rank-1)/(rows-1)
CUME_DIST()用于计算某一行在结果集中的累积分布值。它返回一个介于0和1之间的值,表示当前行在整个结果集中的累积分布比例。<=当前rank值的行数/总行数
前后函数LAG(expr,n)返回当前行的前n行的expr的值
LEAD(expr,n)返回当前行的后n行的expr的值
头尾函数FIRST_VALUE(expr)返回第一个expr的值
LAST_VALUE(expr)返回最后一个expr的值
其他函数NTH_VALUE(expr,n)返回第n个expr的值
NTILE (n)将有序数据分为n个桶,记录等级数

初始化表结构

CREATE TABLE `score_test` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id【updt:0】【Sec:D】【STD:Num】',
  `score` int NOT NULL COMMENT 'score【updt:n】【Sec:D】【STD:date】',
  `class_id` int NOT NULL COMMENT 'class_id【updt:n】【Sec:D】【STD:date】',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='score_test';

INSERT INTO score_test (score, class_id) VALUES(100, 1);
INSERT INTO score_test (score, class_id) VALUES(90, 1);
INSERT INTO score_test (score, class_id) VALUES(59, 1);
INSERT INTO score_test (score, class_id) VALUES(30, 1);
INSERT INTO score_test (score, class_id) VALUES(40, 1);
INSERT INTO score_test (score, class_id) VALUES(20, 2);
INSERT INTO score_test (score, class_id) VALUES(25, 2);
INSERT INTO score_test (score, class_id) VALUES(35, 2);
INSERT INTO score_test (score, class_id) VALUES(55, 2);
INSERT INTO score_test (score, class_id) VALUES(65, 2);
INSERT INTO score_test (score, class_id) VALUES(100, 2);
INSERT INTO score_test (score, class_id) VALUES(85, 3);
INSERT INTO score_test (score, class_id) VALUES(95, 3);
INSERT INTO score_test (score, class_id) VALUES(58, 3);
INSERT INTO score_test (score, class_id) VALUES(58, 4);

原始数据如下

执行开窗函数效果

如果开窗函数中存在partition那么聚合函数将根据分区进行聚合,不同分区聚合结果不一样

如果没有partition那么将对整个结果进行聚合

avg+over+partition

SELECT id,class_id , score, AVG(score) OVER(partition by class_id) AS avg_score
FROM score_test

 max+over+partition

SELECT id,class_id , score, max(score) OVER(partition by class_id) AS avg_score
FROM score_test

 

 min+over+partition

SELECT id,class_id , score, min(score) OVER(partition by class_id) AS avg_score
FROM score_test

 min+over

SELECT id,class_id , score, min(score) OVER() AS avg_score
FROM score_test

 count+over

SELECT id,class_id , score, count(1) OVER() AS avg_score
FROM score_test

sum+over+partition

SELECT id,class_id , score, sum(score) OVER(partition by class_id) AS avg_score
FROM score_test

 row_number+over+partition+orderby

SELECT id,class_id , score, row_number() over(partition by class_id order by score desc) AS avg_score
FROM score_test

 rank,dense_rank,lead,lag,first_value,last_value

SELECT id,class_id ,score,
rank() over( order by score desc) as '<所有数据>排名',
rank() over(partition by class_id order by score desc) as '<当前分区>排名',
dense_rank() over(partition by class_id order by score desc) as '<当前分区>稠密排名',
lead(score,3) over(partition by class_id order by score desc) as '<当前分区>当前分数后两行分数',
lag(score,2) over(partition by class_id order by score desc) as '<当前分区>当前分数前两行分数',
first_value(score) over(partition by class_id order by score desc) as '<当前分区>第一个值',
last_value(score) over(partition by class_id order by score desc) as '<当前分区>最后一个值',
last_value(score) over(partition by class_id order by score desc ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as '<当前分区>最后一个值' -- 注意:LAST_VALUE函数需要指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,以确保获取到最后一个值。
FROM score_test

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐