提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- sql查询各科成绩前三名
- 建表
- 造数
- 方法一:使用加行号的方式查询
- 方法二:使用子查询嵌套查询
- 原理解析
- 考虑并列情况
- 方法三:窗口函数
- 1.ROW_NUMBER
- 2. 不考虑并列情况:rank()
- 3.考虑并列情况:dense_rank()
sql查询各科成绩前三名
建表
create table scores(
name varchar(100),
subject varchar(100),
score int
);
造数
insert into scores values
('学生a','java','100'),
('学生b','java','90'),
('学生c','java','90'),
('学生d','java','60'),
('学生e','java','80'),
('学生a','python','100'),
('学生b','python','90'),
('学生c','python','90'),
('学生d','python','60'),
('学生e','python','80');
方法一:使用加行号的方式查询
- 不考虑并列情况
select score,name,@m1:=@m1+1 r from scores,(select @m1:=0)a where subject='java' order by score desc
查询结果如下
此处加行号m1的作用就可以体现出来,查询语句中多了一个字段 r ,他可以以数字1,2,3,4,5的形式显示排名
由此引申,此条查询语句结尾在加上 limit 3 便可以取出前三名
select score,name,@m1:=@m1+1 r from scores,(select @m1:=0)a where subject='java' order by score desc limit 3
再引申,若要查询所有课程的成绩,取前三名,则就需要将其他的课程表 join 在一起,关联条件为每条查询语句的行号相等
select s1.score "java成绩",s1.name,s2.score "python成绩",s2.name,s1.r "排名" from
(select score,name,@m1:=@m1+1 r from scores,(select @m1:=0)a where subject='java' order by score desc limit 3)s1
join
(select score,name,@m2:=@m2+1 r from scores,(select @m2:=0)b where subject='python' order by score desc limit 3)s2
on s1.r=s2.r;
这种方法查询实际上是 列转行 的方式,将字段subject 列 转成 行 输出。
- 优点 是容易理解,增加了一个字段显示排名,更加直观。
- 缺点 是在关联条件多(比如课程数量大于10,查询每科前10名,前20名成绩…)的情况下, join 关联10次以上,频繁的join会损耗系统很多性能,严重的会直接堵塞死。且有个弊端是写查询语句的时候必须要知道具体有几门课及课程名称,where条件就已经限定了每门课的课程id或者课程名称,但有些情况下表数据量很大的时候,这种方法是不合适的。
方法二:使用子查询嵌套查询
SELECT s1.* FROM scores s1
WHERE (SELECT COUNT(1) FROM scores s2 WHERE s1.subject=s2.subject AND s1.score<s2.score)<3
ORDER BY s1.subject,s1.score DESC;
原理解析
意思是:统计学生个数,即关联两个分数表s1、s2, 外层查询每查询一次,再到内层循环中查询表s2
- 外层表首先查询出一条数据
- 再到内层循环中查询表s2
- 当课程名相同时,统计 s1.score <s2.score 的个数,如果超过3个,证明此时外层的这一行数据的 score 不是此课程的前三名。(因为前三名 ,全班的成绩不应该有超过三个人 比他的分数还高,最多可能是第一名 和第二名比分数高)
- 如果满足(s1.score<s2.score)< 3 就是目标前三名的数据 ,保留下来 ,不满足就被where条件过滤掉
- 外层下一条数据 继续循环
首先从学生a开始查询
此时从学生a到e的 “java” 课程前三名已经筛选完成,对于 “python” 课程,重复上述流程即可
全部筛选完毕最后再对查询出的课程、分数倒序排列即可 :order by s1.subject,s1.score desc
考虑并列情况
elect s1.name,s1.subject,s1.score from scores s1left join (select distinct subject,score from scores) s2on s1.subject=s2.subjectand s1.scoregroup by s1.name,s1.subject,s1.scorehaving count(1)<3order by subject,score desc;
很直观的可以看出,学生b和c成绩都为90分,并列第二名,学生e成绩80分,为第三名
查询语句解析:
- 这是在 2.1方法二 的基础上,使用 distinct 关键字对表s2中存在多名同学分数相同的情况进行去重,从而达到并列排名的目的。需要注意的是,由于groub by 的条件是表s1中的字段,所以 count(1)统计的是表s1中每次查询s1.score
方法三:窗口函数
mysql8 或者 HIVE 才支持窗口函数
MySQL基础–10—MySQL8新特性----窗口函数
1.ROW_NUMBER
SELECT * FROM
(SELECT NAME,SUBJECT,score,ROW_NUMBER() over (PARTITION BY SUBJECT ORDER BY score DESC) ranks FROM scores)s
WHERE ranks<4;
2. 不考虑并列情况:rank()
SELECT * FROM
(SELECT NAME,SUBJECT,score,rank() over (PARTITION BY SUBJECT ORDER BY score DESC) ranks FROM scores)s
WHERE ranks<4;
3.考虑并列情况:dense_rank()
SELECT * FROM
(SELECT NAME,SUBJECT,score,dense_rank() over (PARTITION BY SUBJECT ORDER BY score DESC) ranks FROM scores)s
WHERE ranks<4;