最近几次面试,每一次回来都会感到自己的不足。
比如上一次,一家做教育的公司笔试题里有三道数据库题目,都是关于学生和成绩的查询,看着题目我不禁陷入沉思,人家日常用的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 分数
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】) 字符串拼接