法一
先查询学号为“01”的学生所学课程:
SELECT c_id FROM Score
WHERE s_id = '01'
选出学号不为“01”且选的课程为“01”“02”“03”的同学学号:
SELECT s_id'学号',s_name'姓名' FROM Student
WHERE s_id IN
(
SELECT DISTINCT s_id FROM Score
WHERE c_id IN
(
SELECT c_id FROM Score
WHERE s_id = '01'
) AND s_id != '01'
)
先构造一个内联表:
SELECT *
FROM Student AS a
INNER JOIN
(
SELECT DISTINCT s_id FROM Score
WHERE c_id IN
(
SELECT c_id FROM Score
WHERE s_id = '01'
) AND s_id != '01'
) AS b ON a.s_id = b.s_id
再从中选出学号,姓名的信息:
SELECT a.s_id'学号',a.s_name'姓名'
FROM Student AS a
INNER JOIN
(
SELECT DISTINCT s_id FROM Score
WHERE c_id IN
(
SELECT c_id FROM Score
WHERE s_id = '01'
) AND s_id != '01'
) AS b ON a.s_id = b.s_id
本题小亮老师在视频讲解中第一遍也讲错了,博主认为这个错点确实很难注意到,故在此处强调一下⚠️
易错点1:没有考虑到学号02的情况。
先选取了选了课程01,02,03中某一门的学生学号,再从中选出选了和01学生课程数一样的学生学号,这个做法没有考虑到可能存在课程04,学生选了课程01,02,04。实质上,这个错误是由IN函数是从(01,02,03)中选一个满足就可以而造成的。
易错点2:没有考虑到学号03的情况。
代码从Score表中选出课程为01,02,03的学生学号,相当于只选了下表前三条的学号,也就是选出了3个学号03,因此03号学生也能通过选课数为3的条件,被最终选出来。这个错误实质上没有考虑到可能学生01选的不是全部课程的情况,并且对Score表的排列没有了解——该表把每个学号对应课程成绩拆做一条,因此在选择时,超出01,02,03的会被删除。
正确解题思路:1.查询01号学生选了课程01,02,03;
2.选出所学课不在(01,02,03)中的同学 → 删 除 \rightarrow 删除 →删除;
3.剩下的同学肯定选了(01,02,03)中的某几门课,判断所学的课程数是否等于3,等于3的同学一定是选了课程01,02,03。
选出所学课不在(01,02,03)中的同学:
SELECT s_id FROM Score
WHERE c_id NOT IN
(
SELECT c_id FROM Score
WHERE s_id = '01'
)
剩下的同学肯定选了(01,02,03)中的某几门课,判断所学的课程数是否等于3:
SELECT s_id'学号' FROM Score
WHERE s_id != '01'
GROUP BY s_id
HAVING COUNT(DISTINCT c_id) =
(SELECT COUNT(DISTINCT c_id) FROM Score WHERE s_id = '01')
-- 最后一行语句计算的是学号01的学生共修了几门课
反选2.的情况并和3.的情况做并集:
SELECT s_id'学号' FROM Score
WHERE s_id != '01'
GROUP BY s_id
HAVING COUNT(DISTINCT c_id) =
(SELECT COUNT(DISTINCT c_id) FROM Score WHERE s_id = '01')
AND s_id NOT IN -- 反选
(
SELECT s_id FROM Score
WHERE c_id NOT IN
(
SELECT c_id FROM Score
WHERE s_id = '01'
)
)
法一
解题思路:
选出至少两门课分数小于60分的学生学号:
SELECT s_id FROM Score
WHERE s_score < 60
GROUP BY s_id HAVING COUNT(DISTINCT c_id) >= 2
以学号为标准内联结Student表和Score表,将内联表以学号/姓名作为分类,从中查询满足1.的学号,姓名,并计算平均分。
SELECT a.s_id,a.s_name,AVG(b.s_score)
FROM Student AS a
INNER JOIN
Score AS b ON a.s_id = b.s_id
WHERE a.s_id IN
(
SELECT s_id FROM Score
WHERE s_score < 60
GROUP BY s_id HAVING COUNT(DISTINCT c_id) >= 2
)
GROUP BY s_id,s_name
法二 这是博主自己想的做法,如有错误欢迎提出
解题思路:
选出分数小于60分的学生的学号和课程,查询至少两门课不及格的学生学号、这些学生的平均分:
SELECT s_id,AVG(s_score)'average' FROM Score
WHERE s_score < 60
GROUP BY s_id HAVING COUNT(DISTINCT c_id) >= 2
以学号为标准内联结Student表和1.中表
SELECT a.s_id,a.s_name,b.average
FROM Student AS a
LEFT JOIN
(
SELECT s_id,AVG(s_score)'average' FROM Score
WHERE s_score < 60
GROUP BY s_id HAVING COUNT(DISTINCT c_id) >= 2
) AS b ON a.s_id = b.s_id
选出平均分不为NULL的学号:
SELECT a.s_id,a.s_name,b.average
FROM Student AS a
LEFT JOIN
(
SELECT s_id,AVG(s_score)'average' FROM Score
WHERE s_score < 60
GROUP BY s_id HAVING COUNT(DISTINCT c_id) >= 2
) AS b ON a.s_id = b.s_id
WHERE average IS NOT NULL
易错点:平均分不为NULL不是“average != NULL”,而是“average IS NOT NULL”
SELECT a.s_id'学号',a.s_name'姓名',b.s_score'分数' FROM Student AS a
INNER JOIN Score AS b ON a.s_id = b.s_id
WHERE b.c_id = '01' AND b.s_score < 60
ORDER BY s_score DESC
本题目标是构造如下表格:
难点:如何把每门课的成绩提取出来?
利用CASE WHEN来提取!
SELECT s_id'学号',
MAX(CASE WHEN c_id = '01' THEN s_score ELSE NULL END)'语文',
MAX(CASE WHEN c_id = '02' THEN s_score ELSE NULL END)'数学',
MAX(CASE WHEN c_id = '03' THEN s_score ELSE NULL END)'英文',
AVG(s_score)'平均分'
FROM Score
GROUP BY s_id
ORDER BY AVG(s_score) DESC
MAX函数的调用是因为GROUP BY函数使用后,只能查询GROUP BY函数后的条件以及统计项,此处CASE WHEN后c_id=‘01’只能取出一个分数,所以即使加上MAX依然是该分数。
SELECT
a.c_id,
b.c_name'课程名',
MAX(a.s_score)'最高分',
MIN(a.s_score)'最低分',
AVG(a.s_score)'平均分',
SUM(CASE WHEN a.s_score >= 60 THEN 1 ELSE 0 END)/COUNT(a.s_id)'及格率', -- 由于GROUP BY c_id过,故这里会对每一个c_id做前面这个语句
SUM(CASE WHEN a.s_score >= 70 AND a.s_score <80 THEN 1 ELSE 0 END)/COUNT(a.s_id)'中等率',
SUM(CASE WHEN a.s_score >= 80 AND a.s_score <90 THEN 1 ELSE 0 END)/COUNT(a.s_id)'优良率',
SUM(CASE WHEN a.s_score >= 90 THEN 1 ELSE 0 END)/COUNT(a.s_id)'优秀率'
FROM Score AS a
INNER JOIN Course AS b ON a.c_id = b.c_id
GROUP BY c_id
CASE WHEN宝典:
计算及格率思路:及格率 = count(>=60的学生数)/ count(总人数)
用计数器思路来合计课程及格的人数,当字段满足条件时,将该字段记为‘1’,否则记为‘0’,再将变换后的分数求和,即是满足条件的人数。再除以选课学生人数,完毕。
row_number()
SELECT a.s_id'学号',b.c_name'课程',a.s_score'成绩',
row_number() over(PARTITION BY a.c_id ORDER BY a.s_score DESC)'排名'
FROM Score AS a
INNER JOIN Course AS b ON a.c_id = b.c_id
SELECT a.s_id'学号',b.c_name'课程',a.s_score'成绩',
rank() over(PARTITION BY a.c_id ORDER BY a.s_score DESC)'排名'
FROM Score AS a
INNER JOIN Course AS b ON a.c_id = b.c_id
SELECT a.s_id'学号',b.c_name'课程',a.s_score'成绩',
dense_rank() over(PARTITION BY a.c_id ORDER BY a.s_score DESC)'排名'
FROM Score AS a
INNER JOIN Course AS b ON a.c_id = b.c_id
三种窗口函数的用法都为:窗口函数 + over(PARTITION BY 分类依据(例如,此处分为每门课程来排序) ORDER BY 排序标准(例如,此处根据分数高低来排序) DESC)
SELECT s_id'学号',SUM(s_score)'总成绩' FROM Score
GROUP BY s_id
ORDER BY 总成绩 DESC
知乎-小番茄-SQL面试必会50题https://zhuanlan.zhihu.com/p/43289968 ↩︎
知乎-猴子-常见的SQL面试题:经典50题https://zhuanlan.zhihu.com/p/38354000 ↩︎
mysql8.0窗口函数:rank,dense_rank,row_number 使用上的区别https://yq.aliyun.com/articles/593698 ↩︎