Mysql 经典50题练习

1,709 阅读11分钟

工作之后发现自己已经不太会写sql了,除了业务方面的学习,技术上也不能落下啊。于是打算做一遍这50题,通过实际操作来加深对sql的理解。

CREATE TABLE student (
	sid varchar(10),
	sname varchar(10),
	sage datetime,
	ssex varchar(10)
);

insert into student values('01' , '赵雷' , '1990-01-01' , '男');
insert into student values('02' , '钱电' , '1990-12-21' , '男');
insert into student values('03' , '孙风' , '1990-05-20' , '男');
insert into student values('04' , '李云' , '1990-08-06' , '男');
insert into student values('05' , '周梅' , '1991-12-01' , '女');
insert into student values('06' , '吴兰' , '1992-03-01' , '女');
insert into student values('07' , '郑竹' , '1989-07-01' , '女');
insert into student values('08' , '王菊' , '1990-01-20' , '女');

CREATE TABLE course (
	cid varchar(10),
	cname varchar(10),
	tid varchar(10)
);

insert into course values('01' , '语文' , '02');
insert into course values('02' , '数学' , '01');
insert into course values('03' , '英语' , '03');

CREATE TABLE teacher (
	tid varchar(10),
	tname varchar(10)
);

insert into teacher values('01' , '张三');
insert into teacher values('02' , '李四');
insert into teacher values('03' , '王五');

CREATE TABLE sc (
	sid varchar(10),
	cid varchar(10),
	score decimal(18, 1)
);

insert into sc values('01' , '01' , 80);
insert into sc values('01' , '02' , 90);
insert into sc values('01' , '03' , 99);
insert into sc values('02' , '01' , 70);
insert into sc values('02' , '02' , 60);
insert into sc values('02' , '03' , 80);
insert into sc values('03' , '01' , 80);
insert into sc values('03' , '02' , 80);
insert into sc values('03' , '03' , 80);
insert into sc values('04' , '01' , 50);
insert into sc values('04' , '02' , 30);
insert into sc values('04' , '03' , 20);
insert into sc values('05' , '01' , 76);
insert into sc values('05' , '02' , 87);
insert into sc values('06' , '01' , 31);
insert into sc values('06' , '03' , 34);
insert into sc values('07' , '02' , 89);
insert into sc values('07' , '03' , 98);
  1. 查询"01"课程比"02"课程成绩高的学生的信息及课程分数 keyword : 自连接(SELF JOIN)
SELECT student.*, sc1.score AS 'course-01', sc2.score AS 'course-02'
  FROM student, sc sc1, sc sc2
  WHERE student.sid = sc1.sid
	  AND sc1.sid = sc2.sid
	  AND sc1.cid = '01'
	  AND sc2.cid = '02'
	  AND sc1.score > sc2.score


1.1 查询存在" 01 "课程但可能不存在" 02 "课程的情况(不存在时显示为 null )

keyword : LEFT JOIN

SELECT * 
FROM	
    (SELECT * FROM sc WHERE sc.cid = '01' ) sc1
    LEFT JOIN ( SELECT * FROM sc WHERE sc.cid = '02' ) sc2 ON sc1.sid = sc2.sid


1.2 查询同时存在01和02课程的情况

keyword : 子查询

SELECT *
FROM
    ( SELECT * FROM sc WHERE sc.cid = '01' ) sc1,
    ( SELECT * FROM sc WHERE sc.cid = '02' ) sc2 
WHERE sc1.sid = sc2.sid


1.3 查询选择了02课程但没有01课程的情况

keyword : NOT IN

SELECT * 
FROM sc 
WHERE cid = '02'  AND sid NOT IN ( SELECT sid FROM sc WHERE cid = '01' )


2.查询平均成绩大于等于 60 分的同学的学生编号和学生姓名和平均成绩

keyword : GROUP BY、HAVING

--注意版本默认的model问题
SELECT sc.sid,s.sname,avg(score) 
FROM student s INNER JOIN sc ON s.sid = sc.sid
GROUP BY sid
HAVING (avg(score) >= 60)


3.查询在 SC 表存在成绩的学生信息

keyword : DISTINCT

SELECT DISTINCT student.*
FROM sc,student
WHERE sc.sid = student.sid 


4.查询所有同学的学生编号、学生姓名、选课总数、所有课程的成绩总和

keyword : INNER JOIN、COUNT()、SUM()

SELECT s.sid,s.sname,COUNT(sc.cid),SUM(sc.score)
FROM student s INNER JOIN sc ON s.sid = sc.sid
GROUP BY sc.sid


5.查询「李」姓老师的数量

keyword : COUNT()、LIKE

SELECT COUNT(1)
FROM teacher
WHERE tname LIKE ('李%');


6.查询学过「张三」老师授课的同学的信息

keyword : 多表连接

SELECT s.*
FROM student s INNER JOIN  sc ON s.sid = sc.sid
INNER JOIN course c ON sc.cid = c.cid
INNER JOIN teacher t ON c.tid = t.tid
WHERE t.tname = '张三'


7.查询没有学全所有课程的同学的信息

keyword : LEFT JOIN、HAVING 、COUNT()

SELECT s.*,COUNT(sc.cid)
FROM student s LEFT JOIN sc ON s.sid = sc.sid
GROUP BY sc.sid
HAVING COUNT(sc.cid) < (
    SELECT COUNT(1) FROM course
)


8.查询至少有一门课与学号为" 01 "的同学所学相同的同学的信息

keyword : INNER JOIN、GROUP BY

SELECT s.sid,s.sname
FROM student s INNER JOIN sc on s.sid = sc.sid
WHERE sc.cid IN (
    SELECT cid FROM sc WHERE sid = '01'
) AND s.sid != '01'
GROUP BY sid


9.查询和" 01 "号的同学学习的课程完全相同的其他同学的信息

keyword : GROUP_CONCAT、ORDER BY

SELECT s.sid,sname,ssex,GROUP_CONCAT( cid ORDER BY cid ) 
FROM sc INNER JOIN student s ON sc.sid = s.sid 
GROUP BY sc.sid 
HAVING GROUP_CONCAT(cid ORDER BY cid) = ( 
	SELECT GROUP_CONCAT(cid ORDER BY cid) 
	FROM sc 
	GROUP BY sid 
	HAVING sid = '01') 
AND sid != '01'
-- 然而实际上去掉GROUP_CONCAT中的ORDER BY也一样能得出正确答案
-- 去掉后执行子查询会发现04号的学生得到的课程是"03,01,02"
-- 不太清楚GROUP_CONCAT的拼接规则


10.查询没学过"张三"老师讲授的任一门课程的学生姓名

keyword : NOT IN、LEFT JOIN

SELECT sid,sname 
FROM student 
WHERE sid NOT IN (
	SELECT sid 
	FROM sc LEFT JOIN course ON sc.cid=course.cid
	LEFT JOIN teacher ON course.tid=teacher.tid 
WHERE tname='张三')


11.查询两门及其以上不及格课程的同学的学号,姓名及其平均成绩

keyword : INNER JOIN、GROUP BY、HAVING

SELECT s.sid,s.sname,COUNT(cid),AVG(score)
FROM student s INNER JOIN sc ON s.sid = sc.sid
WHERE sc.score < 60
GROUP BY sc.sid
HAVING COUNT(cid) >= 2


12.检索" 01 "课程分数小于 60,按分数降序排列的学生信息

keyword : ORDER BY

SELECT * FROM student s,sc 
WHERE s.sid = sc.sid 
AND sc.cid = '01' 
AND sc.score < 60
ORDER BY sc.score DESC


13.按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩

keyword : GROUP BY、ORDER BY

SELECT s.sid,s.sname,cid,score,avg_score
FROM student s INNER JOIN sc ON s.sid = sc.sid
LEFT JOIN (SELECT sid ,AVG(score) as avg_score FROM sc GROUP BY sid) t ON s.sid = t.sid
WHERE avg_score is NOT NULL
ORDER BY avg_score DESC


14.查询各科成绩最高分、最低分和平均分,以如下形式显示: 以如下形式显示: 课程 ID,课程 name,最高分,最低分,平均分,及格率,中等率,优良率,优秀率

及格为>=60,中等为:70-80,优良为:80-90,优秀为:>=90 要求输出课程号和选修人数,查询结果按人数降序排列,若人数相同,按课程号升序排列

keyword : GROUP BY、CASE WHEN

SELECT cid as '课程ID',cname as '课程name',COUNT(sid) as '选修人数',
MAX(score) as '最高分',MIN(score) as '最低分',AVG(score) as '平均分',
SUM(及格)/COUNT(sid) AS '及格率',
SUM(中等)/COUNT(sid) AS '中等率',
SUM(优良)/COUNT(sid) AS '优良率',
SUM(优秀)/COUNT(sid) AS '优秀率'
FROM (
	SELECT sc.*,c.cname,
	CASE WHEN sc.score >= 60 THEN 1 ELSE 0 END as '及格',
	CASE WHEN sc.score >= 70 AND score < 80 THEN 1 ELSE 0 END as '中等',
	CASE WHEN sc.score >= 80 AND score < 90 THEN 1 ELSE 0 END as '优良',
	CASE WHEN sc.score >= 90 THEN 1 ELSE 0 END as '优秀'
	FROM sc,course c
	WHERE sc.cid = c.cid
) temp
GROUP BY temp.cid


15.1 按各科成绩进行排序,并显示排名,Score 重复时保留名次空缺 成绩排序题参考如下:

  1. 按各科成绩排序,并显示名次(同名次空缺vs合并)
  2. 一文解决所有MySQL分类排名问题

keyword : 子查询

-- mysql5.7及以下(下文有分析) --
SELECT *, (
		SELECT COUNT(DISTINCT score) + 1
		FROM sc b
		WHERE b.cid = a.cid AND b.score > a.score) AS 名次
FROM sc a
ORDER BY cid, 名次

-----------------------------------------------

-- mysql8.0及以上可以使用窗口函数 --
SELECT 
    *, DENSE_RANK() OVER(PARTITION BY cid ORDER BY score DESC) AS 'rank'
FROM 
    sc

分析:这应该是最直观的排名方法,对a表中每条记录,都会去b表查询比改记录分数高的数量,并且对score排重,保留名次空缺


15.2 按各科成绩进行行排序,并显示排名, Score 重复时合并名次

keyword : 对LEFT JOIN以及 GROUP BY需要有一定的理解

先上最终答案

-- mysql5.7及以下(下文有分析) --
SELECT a.*,COUNT(b.score)+1 AS 'rank'
FROM sc AS a LEFT JOIN sc AS b ON a.cid = b.cid AND a.score < b.score
GROUP BY a.sid,a.cid
ORDER BY a.cid,COUNT(b.score)

-----------------------------------------------

-- mysql8.0及以上可以使用窗口函数 --
SELECT 
    *, RANK() OVER(PARTITION BY cid ORDER BY score DESC) AS 'rank'
FROM 
    sc



接下来是步骤分解,为了方便理解,我们建一张简易版的test表

简易版test表

  1. test表自交,得到cid相等,并且a表中score小于b表score的结果集
SELECT * 
FROM test AS a LEFT JOIN test AS b ON a.cid = b.cid AND a.score < b.score

1

2.先将结果集排序一下,寻找排名的原理

SELECT * 
FROM test AS a LEFT JOIN test AS b ON a.cid = b.cid AND a.score < b.score
ORDER BY a.cid,a.sid

2

分析: 我们直观上看a的语文90分是最高,左边是null。b的语文85分是第二,左边有a-语文。c的语文60分是第三,左边有a-语文,b-语文。结合sql语句中的“test AS a LEFT JOIN test AS b ON a.cid = b.cid AND a.score < b.score”。因此test表自交并且a.score < b.score的意思就是每条a记录对应的就是分数比它高的记录。 我们列出来看看

a 语文 90 (对应为null,说明没有比它高的)

b 语文 85 a 语文 90 (对应有a-语文,说明有a-语文比它高)

c 语文 60 a 语文 90 (对应有a-语文,b-语文,说明有a-语文,b-语文比它高) c 语文 60 b 语文 85

3.从步骤二得到规律之后就应该分组了,只要group by就能获取到对应的COUNT(b.score)作为排名,注意group by的字段选取,我们要通过sid和cid才能获取每个人每个科目的成绩,最终答案如下

SELECT a.*,COUNT(b.score)+1 AS 'rank'
FROM test AS a LEFT JOIN test AS b ON a.cid = b.cid AND a.score < b.score
GROUP BY a.sid,a.cid /*不懂的话可以拿步骤2的数据group by a.sid,a.cid一次*/
ORDER BY a.cid,COUNT(b.score)

3


16.1 查询学生的总成绩,并进行排名,总分重复时保留名次空缺

keyword : 用户变量

SELECT t1.*,
@curRank := IF (@perScore = sum_score,@curRank, @curRank+1) as 'rank',
@perScore := sum_score
FROM 
(SELECT sid,SUM(score) AS sum_score FROM sc GROUP BY sid) AS t1 , 
(SELECT @curRank:=0,@perScore := NULL) AS t2
ORDER BY sum_score DESC 

分析:整体还是比较好理解的,创建两个用户变量@curRank (当前排名)和@perScore(前一个学生的总分),在遍历时比较当前的总分和前一个的总分是否相等,相等的话@curRank 不变,否则@curRank +1,每次遍历都需要更新一次@perScore

为了模拟同分的情况!sid=3,cid=1的分数从80改成50

16.2 查询学生的总成绩,并进行排名,总分重复时不保留名次空缺

keyword : 用户变量

SELECT 
    t1.*, @curRank := IF(sum_score=@preScore, @curRank, @totalRank) AS 'rank',
    @preScore := sum_score,
    @totalRank := @totalRank+1
FROM 
    (SELECT sid,SUM(score) AS sum_score FROM sc GROUP BY sid) AS t1,
    (SELECT @curRank:=1, @totalRank:=1, @preScore:=NULL) t2
ORDER BY sum_score DESC

分析:和上一题基本一致,再加多了一个用户变量@totalRank,每次遍历都+1,然后当前总分和上一个学生总分相同时,排名是@curRank不变,否则 更新@curRank为@totalRank 为了模拟同分的情况,sid=3,cid=1的分数我从80改成50

  1. 统计各科成绩各分数段人数:课程编号,课程名称,[100-85],[85-70],[70-60],[60-0] 及所占百分比 keyword : CASE WHEN
SELECT
	sc.cid AS 课程编号,
	cname AS 课程名称,
	SUM( CASE WHEN score >= 0 AND score <= 60 THEN 1 ELSE 0 END ) AS '[60-0]',
	SUM( CASE WHEN score >= 0 AND score <= 60 THEN 1 ELSE 0 END ) / COUNT( sid ) AS '[60-0]百分比',
	SUM( CASE WHEN score >= 60 AND score <= 70 THEN 1 ELSE 0 END ) AS '[70-60]',
	SUM( CASE WHEN score >= 60 AND score <= 70 THEN 1 ELSE 0 END ) / COUNT( sid ) AS '[70-60]百分比',
	SUM( CASE WHEN score >= 70 AND score <= 85 THEN 1 ELSE 0 END ) AS '[85-70]',
	SUM( CASE WHEN score >= 70 AND score <= 85 THEN 1 ELSE 0 END ) / COUNT( sid ) AS '[85-70]百分比',
	SUM( CASE WHEN score >= 85 AND score <= 100 THEN 1 ELSE 0 END ) AS '[100-85]',
	SUM( CASE WHEN score >= 85 AND score <= 100 THEN 1 ELSE 0 END ) / COUNT( sid ) AS '[100-85]百分比' 
FROM
	sc JOIN course ON sc.cid = course.cid 
GROUP BY sc.cid,cname


  1. 查询各科成绩前三名的记录 keyword :HAVING
SELECT *, (
        SELECT COUNT(score) + 1
        FROM sc b
        WHERE b.cid = a.cid AND b.score > a.score) AS ranking
FROM sc a
HAVING ranking <= 3
ORDER BY cid, ranking


  1. 查询每门课程被选修的学生数 keyword : GROUP BY
SELECT cid,COUNT(sid) AS num
FROM  sc 
GROUP BY cid


  1. 查询出只选修两门课程的学生学号和姓名 keyword : INNER JOIN、HAVING
SELECT sc.sid,sname
FROM sc INNER JOIN student ON sc.sid = student.sid
GROUP BY sc.sid
HAVING COUNT(cid) = 2


  1. 查询男生、女生人数 keyword : GROUP BY
SELECT ssex,COUNT(sid) as num
FROM student
GROUP BY ssex


  1. 查询名字中含有「风」字的学生信息 keyword : LIKE
SELECT *
FROM student
WHERE sname LIKE '%风%'


  1. 查询同名同性学生名单,并统计同名人数 keyword : LEFT JOIN、GROUP BY
SELECT a.sname,a.ssex,COUNT(1) AS 人数 
FROM student a LEFT JOIN student b ON a.sname=b.sname 
WHERE a.ssex=b.ssex AND a.sid!=b.sid
GROUP BY a.sname,a.ssex


  1. 查询 1990 年出生的学生名单 keyword : 时间函数
SELECT * 
FROM student 
WHERE YEAR (sage) = '1990'


  1. 查询每门课程的平均成绩,结果按平均成绩降序排列,平均成绩相同时,按课程编号升序排列 keyword : ORDER BY
SELECT cid,AVG(score) as avgScore
FROM sc
GROUP BY cid
ORDER BY avgScore DESC,cid ASC


  1. 查询平均成绩大于等于 85 的所有学生的学号、姓名和平均成绩 keyword : AVG()、HAVING
SELECT s.sid,sname,AVG(score) as avgScore
FROM sc,student s
WHERE sc.sid = s.sid
GROUP BY sid
HAVING avgScore > 85


  1. 查询课程名称为「数学」,且分数低于 60 的学生姓名和分数 keyword : INNER JOIN
SELECT s.sname,sc.score
FROM student AS s INNER JOIN sc ON s.sid = sc.sid
WHERE sc.score < 60 
AND sc.cid IN (SELECT cid FROM course WHERE cname = '数学')


  1. 查询所有学生的课程及分数情况(存在学生没成绩,没选课的情况) keyword : INNER JOIN
SELECT sname,cid,score 
FROM sc INNER JOIN student AS s ON sc.sid = s.sid 


  1. 查询任何一门课程成绩在 70 分以上的姓名、课程名称和分数 keyword : INNER JOIN
SELECT sname,cid,score 
FROM sc INNER JOIN student AS s ON sc.sid = s.sid 
WHERE score > 70


  1. 查询不及格的课程 keyword : DISTINCT
SELECT DISTINCT sc.cid
FROM sc INNER JOIN student AS s ON sc.sid = s.sid 
WHERE score < 60 


31.查询课程编号为 01 且课程成绩在 80 分以上的学生的学号和姓名

keyword : INNER JOIN

SELECT s.sid,sname
FROM sc INNER JOIN student AS s ON sc.sid = s.sid
WHERE sc.cid = '01' AND score >= 80


32.求每门课程的学生人数

keyword :GROUP BY、COUNT()

SELECT cid,COUNT(sid) AS num
FROM sc
GROUP BY cid


33.成绩不重复的情况下,查询选修「张三」老师所授课程的学生中,成绩最高的学生信息及其成绩

keyword :INNER JOIN、MAX()

SELECT *,MAX(score)
FROM student s INNER JOIN sc ON s.sid =sc.sid
WHERE sc.cid IN (
	SELECT cid
	FROM course c,teacher t
	WHERE c.tid = t.tid AND t.tname = '张三'
)


34.成绩重复的情况下,查询选修「张三」老师所授课程的学生中,成绩最高的学生信息及其成绩 keyword :多重JOIN、MAX()

SELECT student.*, sc.cid, score
FROM student
	INNER JOIN sc ON student.sid = sc.sid
	LEFT JOIN course ON sc.cid = course.cid
	LEFT JOIN teacher ON course.tid = teacher.tid
WHERE tname = '张三'
	AND score = (
		SELECT MAX(score)
		FROM sc
			INNER JOIN course ON sc.cid = course.cid
			LEFT JOIN teacher ON course.tid = teacher.tid
		WHERE tname = '张三'
	)

为了模拟同分的情况,sid=7,cid=2的分数我从89改成90


35.查询不同课程成绩相同的学生的学生编号、课程编号、学生成绩

keyword :DISTINCT

SELECT DISTINCT a.* 
FROM sc AS a INNER JOIN sc AS b 
WHERE a.score = b.score AND a.cid != b.cid


  1. 查询每门成绩最好的前两名 keyword :窗口函数
SELECT * 
FROM
	( SELECT *, RANK ( ) OVER ( PARTITION BY cid ORDER BY score DESC ) AS ranking FROM sc ) t
WHERE ranking <= 2


37.统计每门课程的学生选修人数(超过5人的课程才统计)

keyword :GROUP BY、HAVING

SELECT cid,COUNT(sid) 
FROM sc 
GROUP BY cid 
HAVING COUNT(sid) > 5 


38.检索至少选修两门课程的学生学号

*keyword :GROUP BY、HAVING *

SELECT sid,COUNT( cid ) 
FROM sc 
GROUP BY sid 
HAVING COUNT( cid ) >= 2


39.查询选修了全部课程的学生信息

keyword:INNER JOIN

SELECT s.* 
FROM sc INNER JOIN student AS s ON sc.sid = s.sid 
WHERE cid = ( SELECT COUNT( 1 ) FROM course );


40.查询各学生的年龄,只按年份来算 keyword:YEAR()

SELECT sname,YEAR(NOW()) - YEAR(sage) AS 年龄 
FROM student


  1. 按照出生日期来算,当前月日 < 出生年月的月日则,年龄减一

keyword:CASE WHEN、YEAR()

SELECT sname,
	CASE WHEN ( DATE_FORMAT( NOW( ), '%m-%d' ) - DATE_FORMAT( sage, '%m-%d' ) ) < 0 
	THEN YEAR ( NOW( ) ) - YEAR ( sage ) - 1 
	ELSE YEAR ( NOW( ) ) - YEAR ( sage ) 
	END AS 年龄 
FROM student


42.查询本周过生日的学生

keyword:WEEK()

SELECT sname
FROM student
WHERE WEEK(sage) = WEEK(NOW())


43.查询下周过生日的学生

keyword:WEEK()

SELECT sname
FROM student
WHERE WEEK(sage) = WEEK(NOW()) + 1


44.查询本月过生日的学生

keyword:MONTH ()

SELECT sname,sage
FROM student 
WHERE MONTH (sage) = MONTH (NOW())


45.查询下月过生日的学生

keyword:MONTH ()

SELECT sname,sage
FROM student 
WHERE MONTH (sage) = MONTH (NOW()) + 1

结语:终于做完了(有一些题目是有小题的,总体还是有50题的),如果有错误或者更好的方法,欢迎指出