经典SQL练习——详细到令人发指(未完待续)

最近几次面试,每一次回来都会感到自己的不足。
比如上一次,一家做教育的公司笔试题里有三道数据库题目,都是关于学生和成绩的查询,看着题目我不禁陷入沉思,人家日常用的SQL语句我都写不来的话,那我的数据库是不是白学了。
由于用时太长,最后还是面试官过来问我:做完没有。
无地自容的我几乎想当场逃走。
好在回来谷歌一下,发现是知乎上的SQL原题,而知乎又是转载自CSDN。
不禁有一种原来如此的感觉,随之决定也做一下这个全套练习,
为贯彻费曼学习法,尽量以“真正的初学者”作为目标读者。
PS:基于MySQL5.7.12
原文链接(csdn):

超经典SQL练习题,做完这些你的SQL就过关了

测试表格

–1.学生表
Student(S#,Sname,Sage,Ssex)

–S# 学生编号,Sname 学生姓名,Sage 出生年月,Ssex 学生性别

–2.课程表
Course(C#,Cname,T#)

–C# --课程编号,Cname 课程名称,T# 教师编号

–3.教师表
Teacher(T#,Tname)

–T# 教师编号,Tname 教师姓名

–4.成绩表
SC(S#,C#,score)

–S# 学生编号,C# 课程编号,score 分数

  1. 查询" 01 “课程比” 02 "课程成绩高的学生的信息及课程分数
SELECT * FROM student AS t1
INNER JOIN 
(SELECT SId, score AS '01score' FROM sc 
WHERE sc.`CId` = '01') AS t2 
ON t1.SId = t2.SId
INNER JOIN (SELECT SId, score AS '02score' FROM sc 
WHERE sc.`CId` = '02') AS t3 
ON t1.SId = t3.SId AND t2.01score > t3.02score


基础知识点:select 【列名】 from 【表名】(查询语句)
【列名/表名】 AS 【别名】(给列、表起一个别名,类似于简写,可省略)
【查询语句】 where 【查询条件】(指定查询条件)
知识点1:select 查询出来的内容可以当做一张表
知识点2:inner join 【表名】 on 【条件】(inner可省略)
返回多个表中有符合条件的行(至少符合一个条件)
PS:做完发现很多大佬做出来都不带学生信息的,只有学生ID和课程分数,是我画蛇添足了吗

-- 1.1 查询同时存在" 01 "课程和" 02 "课程的情况
SELECT * FROM
(SELECT SId, score AS '01score' FROM sc 
WHERE sc.`CId` = '01') AS t1
INNER JOIN (SELECT SId, score AS '01score' FROM sc 
WHERE sc.`CId` = '02') AS t2 ON t1.SId = t2.SId 

-- 当然还有更简单的
SELECT * FROM sc AS t1,sc AS t2 
WHERE t1.CId = '01' AND t2.CId = '02' 
AND t1.SId = t2.SId

同上

-- 1.2 查询存在" 01 "课程但可能不存在" 02 "课程的情况(不存在时显示为 null )
SELECT * FROM
(SELECT SId, score AS '01score' FROM sc WHERE sc.`CId` = '01') AS t1
LEFT JOIN (SELECT SId, score AS '01score' FROM sc WHERE sc.`CId` = '02') AS t2 ON t1.SId = t2.SId 

知识点3:left join 【表名】on 【条件】(左连接),与inner join 类似,但会返回所有左表和右表有符合条件的部分,如果右表中没有符合条件的部分则显示为null

-- 1.3 查询不存在" 01 "课程但存在" 02 "课程的情况
SELECT * FROM sc
WHERE sc.`SId` NOT IN (SELECT SId FROM sc WHERE CId = '01')
AND sc.`CId` = 02

知识点4: in和not in,IN 操作符允许您在 WHERE 子句中规定多个值。

-- 2.查询平均成绩大于等于 60 分的同学的学生编号和学生姓名和平均成绩
SELECT t1.SId,t1.Sname,t2.avgscore
FROM student AS t1,
(SELECT SId, AVG(score) AS avgscore FROM sc
GROUP BY SId HAVING avgscore >= 60) AS t2
WHERE t1.SId = t2.SId 
-- and t2.avgscore >=60

知识点4:平均数函数AVG(【列名】),计算指定范围中的平均数
知识点5:【查询语句】 group by 【列名】(根据列名为查询分组,一般用于求和求平均数等,必须放在where子句之后,后面的列名必须在查询语句的列名中)
知识点6: having【函数(列名)】,where不能和函数一起使用,所以查询条件涉及函数的需要用到having

-- 3. 查询在 SC 表存在成绩的学生信息
SELECT t1.* FROM student AS t1
WHERE t1.SId IN (SELECT t2.SId FROM sc AS t2)

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

知识点7:distinct 关键字用于返回唯一不同的值,简单来说就是去重,上面的是我的答案,下方的是网上的参考答案,大家觉得这里需要去重吗

-- 4. 查询所有同学的学生编号、学生姓名、选课总数、所有课程的总成绩(没成绩的显示为 null )
SELECT t1.SId AS '学生编号',t1.Sname AS '学生姓名',
COUNT(t2.CId) AS '选课总数',SUM(t2.score) AS '总成绩' 
FROM student AS t1, sc AS t2
WHERE t1.SId = t2.SId GROUP BY t1.SId, t1.Sname

知识点8:常用函数两个:count(列名):计数;sum(列名)求和
知识点9:别名可以用汉字没错,但总觉得没什么实际的作用,将查询结果视为一个表的时候,别名就成为这个表的新列名,汉字作为列名传到后端简直愚蠢,当然如果哪位大佬知道有什么实际应用场景还望告知,感激不尽

-- 4.1 查有成绩的学生信息
SELECT DISTINCT t1.* FROM student AS t1
INNER JOIN sc AS t2 ON t1.`SId` = t2.`SId`

SELECT *
FROM student
WHERE EXISTS(SELECT * FROM sc WHERE student.SId=sc.SId)

这个。。和3有什么区别
知识点10:extsts(查询子句)如果子句中有一条或多条记录则返回true,否则返回false

-- 5.查询「李」姓老师的数量 
SELECT COUNT(1) FROM teacher WHERE Tname LIKE '李%'

知识点11:【列名】 like 【字符串】(用于模糊查询,字符串中用%代表容易个字符,如‘李%’代表以李开头的字符串)

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

SELECT t1.* FROM student t1
INNER JOIN sc t2 ON t2.`SId` = t1.`SId`
INNER JOIN course t3 ON t3.CId = t2.`CId`
INNER JOIN teacher t4 ON t4.TId = t3.TId AND t4.Tname = '张三'

好像没什么新知识点了,,展示一下省略as的效果吧

--7.查询没有学全所有课程的同学的信息
SELECT t1.* FROM student t1
INNER JOIN sc t2 ON t2.`SId` = t1.`SId` 
GROUP BY t1.`SId`
HAVING COUNT(t2.`CId`) < (SELECT COUNT(1) FROM course)

依旧没什么新知识点,但这个要相对复杂些,报错好几次

-- 8. 查询至少有一门课与学号为" 01 "的同学所学相同的同学的信息
SELECT DISTINCT t1.* FROM student t1 
INNER JOIN sc t2 ON t1.`SId` = t2.`SId` AND t2.`CId` IN (
SELECT CId FROM sc WHERE SId = '01')

-- 9. 查询和" 01 "号的同学学习的课程完全相同的其他同学的信息
SELECT * FROM student WHERE sid IN 
(SELECT sid FROM sc WHERE sid NOT IN -- 3. 除T2之外的人(等于或小于)
(SELECT sid FROM sc WHERE cid NOT IN ( -- 2. 所学课程中有不在T1中的课程(大于或不等于T1)的人 T2
SELECT cid FROM sc WHERE sid='01')) -- 1. 01所学的课程表T1
GROUP BY sid 
HAVING COUNT(*)=(SELECT COUNT(*) FROM sc WHERE sid='01') -- 4. 课程数相同
AND sid != '01');

这道可能是一道公认的难题,甚至不少博主包括原作者都轻描淡写地写了错误的答案上去,上文是比较公认的一种解题思路,虽然看起来也很繁琐,但已经是我能找到最简单的了。
建议由内向外理解,步骤按1.2.3.4.

-- 10.查询没学过"张三"老师讲授的任一门课程的学生姓名
SELECT t1.`Sname` FROM student t1
INNER JOIN sc t2 ON t1.`SId` = t2.`SId`
INNER JOIN course t3 ON t2.`CId` = t3.`CId`
INNER JOIN teacher t4 ON t4.`TId` = t3.`TId` AND t4.`Tname` = '张三'

思路简单,轻松愉快

-- 11.查询两门及其以上不及格课程的同学的学号,姓名及其平均成绩
SELECT t1.`SId`,t1.`Sname`,t2.avgscore FROM student t1
INNER JOIN (
SELECT sc.*,AVG(sc.`score`) AS avgscore,COUNT(1) AS countnum FROM sc 
WHERE score < 60 GROUP BY sc.Sid HAVING countnum >=2) t2 
ON t2.`SId` = t1.`SId`
-- 12.检索" 01 "课程分数小于 60 ,按分数降序排列的学生信息
SELECT * FROM student t1 
INNER JOIN sc t2 ON t1.`SId` = t2.`SId` 
AND t2.`CId` = '01' AND t2.`score` < 60
ORDER BY t2.`score` DESC

知识点12: order by 【列名】 desc/asc (按【列名】降序/升序排列)

-- 13. 按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩
SELECT * FROM sc t1
JOIN (SELECT SId,AVG(score) AS avgscore 
FROM sc GROUP BY SId) AS t2
ON t1.`SId` = t2.`SId` ORDER BY t2.avgscore DESC

和上一题类似

-- 14. 查询各科成绩最高分、最低分和平均分:

-- 以如下形式显示:课程 ID,课程 name,最高分,最低分,平均分,及格率,中等率,优良率,优秀率

-- 及格为>=60,中等为:70-80,优良为:80-90,优秀为:>=90

-- 要求输出课程号和选修人数,查询结果按人数降序排列,若人数相同,按课程号升序排列

SELECT t1.`CId` AS '课程ID',t1.Cname AS '课程name',t2.*
FROM course t1
INNER JOIN (
	SELECT CId, MAX(score) AS '最高分',MIN(score) AS '最低分',AVG(score) AS '平均分',
	SUM(CASE WHEN score >=60 THEN 1 ELSE 0 END)/COUNT(1)  AS '及格率',
	SUM(CASE WHEN score >=70 AND score < 80 THEN 1 ELSE 0 END)/COUNT(1) AS '中等率',
	SUM(CASE WHEN score >=80 AND score < 90 THEN 1 ELSE 0 END)/COUNT(1) AS '优良率',
	SUM(CASE WHEN score >=90 THEN 1 ELSE 0 END)/COUNT(1) AS '优秀率' ,
	COUNT(1) AS '选修人数'
	FROM sc GROUP BY CId
) AS t2 ON t1.CId = t2.`CId` ORDER BY '选修人数','课程ID' DESC

知识点13:max(列名):求最大值;
min(列名): 求最小值;
知识点14:case when 【条件1】and 【条件2】… then 【条件为真的输出】 else 【条件为假的输出】 end
(相当于编程中的if/else)
可以看出本题繁而不难,一步一步来就能写出来了。

-- 15. 按各科成绩进行排序,并显示排名, Score 重复时保留名次空缺

15题卡住了,,会了再续

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

知识点15:concat(【元素1】【元素2】) 字符串拼接

你可能感兴趣的:(mysql,sql,数据库,面试)