MySQL 基础练习(50 题完整解析)
MySQL 基础练习(50 题完整解析)
1. 数据准备
建表
CREATE TABLE Student(SId VARCHAR(10),Sname VARCHAR(10),Sage DATETIME,Ssex VARCHAR(10)
);CREATE TABLE Course(CId VARCHAR(10),Cname NVARCHAR(10),TId VARCHAR(10)
);CREATE TABLE Teacher(TId VARCHAR(10),Tname VARCHAR(10)
);CREATE TABLE SC(SId VARCHAR(10),CId VARCHAR(10),score DECIMAL(18,1)
);
插入数据
-- 学生表
INSERT INTO Student VALUES('01','赵雷','1990-01-01','男');
INSERT INTO Student VALUES('02','钱电','1990-12-21','男');
INSERT INTO Student VALUES('03','孙风','1990-12-20','男');
INSERT INTO Student VALUES('04','李云','1990-12-06','男');
INSERT INTO Student VALUES('05','周梅','1991-12-01','女');
INSERT INTO Student VALUES('06','吴兰','1992-01-01','女');
INSERT INTO Student VALUES('07','郑竹','1989-01-01','女');
INSERT INTO Student VALUES('09','张三','2017-12-20','女');
INSERT INTO Student VALUES('10','李四','2017-12-25','女');
INSERT INTO Student VALUES('11','李四','2012-06-06','女');
INSERT INTO Student VALUES('12','赵六','2013-06-13','女');
INSERT INTO Student VALUES('13','孙七','2014-06-01','女');-- 课程表
INSERT INTO Course VALUES('01','语文','02');
INSERT INTO Course VALUES('02','数学','01');
INSERT INTO Course VALUES('03','英语','03');-- 教师表
INSERT INTO Teacher VALUES('01','张三');
INSERT INTO Teacher VALUES('02','李四');
INSERT INTO Teacher VALUES('03','王五');-- 成绩表
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);
2. 练习题与答案
1. 查询"01"课程比"02"课程成绩高的学生的信息及课程分数
SELECT s.*, sc1.score AS score01, sc2.score AS score02
FROM Student s
JOIN SC sc1 ON s.SId = sc1.SId AND sc1.CId = '01'
JOIN SC sc2 ON s.SId = sc2.SId AND sc2.CId = '02'
WHERE sc1.score > sc2.score;
解析:用同一张 SC 表做两次连接,比较两个课程成绩。
语义逐行拆解
SELECT s.*, sc1.score AS score01, sc2.score AS score02
返回
Student
表中该行的所有列(设置别名s
表示),并额外返回两列score01
(学号对应的 01 课程分数)和score02
(对应的 02 课程分数)。
FROM Student s
以
Student
表为基础表(别名s
)。
JOIN SC sc1 ON s.SId = sc1.SId AND sc1.CId = '01'
对
SC
表做一次 内连接(INNER JOIN),设置别名为sc1
,并且在ON
中同时指定了连接条件s.SId = sc1.SId
与sc1.CId = '01'
。也就是说
sc1
只包含课程为01
的成绩记录,且只连接那些Student
有01
课程成绩的行。
JOIN SC sc2 ON s.SId = sc2.SId AND sc2.CId = '02'
再对
SC
表做一次内连接(设置别名sc2
),sc2
只包含课程为02
的成绩记录;因此只有同时有01
和02
两条成绩记录的学生才会出现在中间结果中。
WHERE sc1.score > sc2.score
过滤条件:
01
课程的分数必须严格大于02
课程的分数。只有满足该条件的行才返回。
2. 查询同时存在 "01" 课程和 "02" 课程的情况
SELECT sc1.SId
FROM SC sc1
JOIN SC sc2 ON sc1.SId = sc2.SId
WHERE sc1.CId = '01' AND sc2.CId = '02';
解析:
- 核心逻辑:通过自连接(SC 表自己关联自己),将同一学生(
sc1.SId = sc2.SId
)的两条选课记录关联起来。 - 条件筛选:
sc1.CId = '01'
确保第一条记录是 “01” 课程,sc2.CId = '02'
确保第二条记录是 “02” 课程,两者结合即找到同时选了两门课的学生 ID。 - 应用场景:适用于需要验证 “用户同时满足两个条件” 的场景(如同时购买 A 和 B 商品的用户)。
3. 查询存在 "01" 课程但可能不存在 "02" 课程的情况
SELECT sc1.SId, sc1.score AS score01, sc2.score AS score02
FROM SC sc1
LEFT JOIN SC sc2 ON sc1.SId = sc2.SId AND sc2.CId = '02'
WHERE sc1.CId = '01';
解析:
- 连接逻辑:
LEFT JOIN
保证 “01” 课程的学生记录(sc1
)全部保留,即使该学生没选 “02” 课程,sc2
的字段(如score02
)会显示为NULL
。 - 条件设计:
ON
子句中sc2.CId = '02'
限定关联的是 “02” 课程记录,而非WHERE
子句(若放WHERE
会过滤掉sc2
为NULL
的记录,失去 LEFT JOIN 的意义)。 - 结果意义:返回所有选了 “01” 课程的学生,同时显示他们 “02” 课程的成绩(若有),方便对比同一学生两门课的表现。
4. 查询不存在 "01" 课程但存在 "02" 课程的情况
SELECT sc2.SId, sc2.score AS score02
FROM SC sc2
LEFT JOIN SC sc1 ON sc2.SId = sc1.SId AND sc1.CId = '01'
WHERE sc2.CId = '02' AND sc1.SId IS NULL;
解析:
- 核心思路:先通过
LEFT JOIN
尝试将 “02” 课程的学生(sc2
)与 “01” 课程记录(sc1
)关联,若关联失败(sc1.SId IS NULL
),说明该学生没有 “01” 课程记录。 - 条件验证:
sc2.CId = '02'
确保基础是 “02” 课程的学生,sc1.SId IS NULL
排除有 “01” 课程的学生,最终得到 “只选了 02 但没选 01” 的学生。
5. 查询平均成绩 ≥ 60 的学生学号、姓名、平均成绩
SELECT s.SId, s.Sname, AVG(sc.score) AS avg_score
FROM Student s
JOIN SC sc ON s.SId = sc.SId
GROUP BY s.SId, s.Sname
HAVING AVG(sc.score) >= 60;
解析:
- 分组与聚合:
GROUP BY s.SId, s.Sname
按学生分组(SId
唯一标识学生,Sname
随分组保留),AVG(sc.score)
计算每组(学生)的平均成绩。 HAVING
作用:用于分组后筛选(与WHERE
的区别:WHERE
在分组前过滤行,HAVING
在分组后过滤组)。此处需先计算平均分再筛选达标的学生。- 标准 SQL 要求:
SELECT
中非聚合字段(如s.SId
、s.Sname
)必须出现在GROUP BY
中,否则结果无意义。
6. 查询在 SC 表存在成绩的学生信息
SELECT DISTINCT s.*
FROM Student s
JOIN SC sc ON s.SId = sc.SId;
解析:
- 连接目的:
JOIN SC
确保只保留有选课记录(即有成绩)的学生。 DISTINCT
必要性:一个学生可能选多门课(SC 表有多条记录),JOIN
后会生成重复的学生信息,DISTINCT
去重确保每个学生只显示一次。
7. 查询所有学生的学号、姓名、选课总数、总成绩
SELECT s.SId, s.Sname, COUNT(sc.CId) AS course_count, SUM(sc.score) AS total_score
FROM Student s
LEFT JOIN SC sc ON s.SId = sc.SId
GROUP BY s.SId, s.Sname;
解析:
LEFT JOIN
关键作用:保留所有学生(包括未选课的学生),未选课的学生因sc.CId
为NULL
,COUNT(sc.CId)
会统计为 0,SUM(sc.score)
会统计为NULL
(可通过IFNULL
转为 0)。- 聚合逻辑:
COUNT(sc.CId)
统计选课数量(COUNT(*)
会包含NULL
,此处需排除),SUM(sc.score)
累加所有课程成绩。
8. 查询姓「李」的老师数量
SELECT COUNT(*) AS teacher_count
FROM Teacher
WHERE Tname LIKE '李%';
解析:
- 通配符
LIKE
:'李%'
中%
匹配任意长度的字符(包括 0 个),表示 “以‘李’开头的姓名”,精准筛选所有姓李的老师。 COUNT(*)
:统计符合条件的记录总数,即姓李的老师人数。
9. 查询学过「张三」老师授课的学生信息
SELECT DISTINCT s.*
FROM Student s
JOIN SC sc ON s.SId = sc.SId
JOIN Course c ON sc.CId = c.CId
JOIN Teacher t ON c.TId = t.TId
WHERE t.Tname = '张三';
解析:
- 多表连接链:
Student → SC → Course → Teacher
,通过关联找到 “学生→选课→课程→授课老师” 的关系,最终定位到张三老师的课程。 DISTINCT
去重:一个学生可能选多门张三老师的课,JOIN
后会生成多条重复的学生记录,需去重。
10. 查询没有学全所有课程的学生信息
SELECT *
FROM Student
WHERE SId NOT IN (SELECT SIdFROM SCGROUP BY SIdHAVING COUNT(DISTINCT CId) = (SELECT COUNT(*) FROM Course)
);
解析:
- 子查询逻辑:内层子查询先找出 “学全所有课程” 的学生 —— 按学生分组后,若其选修的不同课程数(
COUNT(DISTINCT CId)
)等于总课程数(SELECT COUNT(*) FROM Course
),则为 “学全”。 - 外层取反:
NOT IN
排除 “学全” 的学生,得到 “未学全” 的学生信息。
11. 查询至少有一门课与学号为 "01" 的同学所学相同的同学信息
SELECT DISTINCT s.*
FROM Student s
JOIN SC sc ON s.SId = sc.SId
WHERE sc.CId IN (SELECT CId FROM SC WHERE SId = '01'
) AND s.SId <> '01';
解析:
- 子查询定位:
SELECT CId FROM SC WHERE SId = '01'
先找出 01 同学学过的所有课程 ID。 - 外层匹配:
sc.CId IN (...)
筛选出选了这些课程的学生,s.SId <> '01'
排除 01 同学本人,DISTINCT
去重(同一学生可能选多门重合课程)。
12. 查询和 "01" 号同学学习的课程完全相同的其他同学信息
SELECT s.*
FROM Student s
WHERE s.SId <> '01'
AND NOT EXISTS (SELECT CId FROM SC WHERE SId = '01'EXCEPTSELECT CId FROM SC WHERE SId = s.SId
)
AND NOT EXISTS (SELECT CId FROM SC WHERE SId = s.SIdEXCEPTSELECT CId FROM SC WHERE SId = '01'
);
解析:
- 集合差集
EXCEPT
:返回第一个集合中有但第二个集合中没有的记录(如 A 课程集EXCEPT
B 课程集,得到 A 有而 B 没有的课程)。 - 双重
NOT EXISTS
:- 第一个
NOT EXISTS
:01 的课程 减去 该学生的课程 结果为空(即该学生包含 01 的所有课程)。 - 第二个
NOT EXISTS
:该学生的课程 减去 01 的课程 结果为空(即该学生没有 01 以外的课程)。 - 两者结合:该学生与 01 的课程完全一致(集合相等)。
- 第一个
13. 查询没学过 "张三" 老师任一课程的学生姓名
SELECT s.Sname
FROM Student s
WHERE s.SId NOT IN (SELECT sc.SIdFROM SC scJOIN Course c ON sc.CId = c.CIdJOIN Teacher t ON c.TId = t.TIdWHERE t.Tname = '张三'
);
解析:
- 子查询逻辑:通过
SC → Course → Teacher
关联,找出所有选过张三老师课程的学生 ID。 - 外层排除:
NOT IN
筛选出不在子查询结果中的学生,即 “没学过张三任何课程” 的学生姓名。
14. 查询两门及以上不及格课程的学生学号、姓名及平均成绩
SELECT s.SId, s.Sname, AVG(sc.score) AS avg_score
FROM Student s
JOIN SC sc ON s.SId = sc.SId
WHERE sc.score < 60
GROUP BY s.SId, s.Sname
HAVING COUNT(*) >= 2;
解析:
- 分步筛选:
WHERE sc.score < 60
先过滤出所有不及格的成绩记录。- 按学生分组后,
COUNT(*)
统计每个学生的不及格课程数。 HAVING COUNT(*) >= 2
筛选出至少两门不及格的学生,并计算他们的平均成绩(含及格和不及格课程)。
15. 检索 "01" 课程分数 < 60,按分数降序排列的学生信息
SELECT s.*, sc.score
FROM Student s
JOIN SC sc ON s.SId = sc.SId
WHERE sc.CId = '01' AND sc.score < 60
ORDER BY sc.score DESC;
解析:
- 条件定位:
sc.CId = '01' AND sc.score < 60
精准找到 01 课程不及格的学生。 - 排序目的:
ORDER BY sc.score DESC
按分数从高到低排列,方便快速查看不及格学生中分数较高(接近及格)的群体。
16. 按平均成绩从高到低显示所有学生的课程成绩及平均成绩
SELECT s.SId, s.Sname, c.Cname, sc.score, t.avg_score
FROM SC sc
JOIN Student s ON sc.SId = s.SId
JOIN Course c ON sc.CId = c.CId
JOIN (SELECT SId, AVG(score) AS avg_scoreFROM SC GROUP BY SId
) t ON s.SId = t.SId
ORDER BY t.avg_score DESC;
解析:
- 子查询作用:提前计算每个学生的平均成绩(
t.avg_score
),避免重复计算,提升效率。 - 关联逻辑:将学生的单科成绩(
sc.score
)与平均成绩(t.avg_score
)关联,方便同时查看 “单科表现” 和 “整体水平”,并按平均分排序。
17. 查询各科成绩最高分、最低分、平均分、及格率等
SELECT c.CId, c.Cname,MAX(sc.score) AS max_score,MIN(sc.score) AS min_score,AVG(sc.score) AS avg_score,SUM(sc.score >= 60)/COUNT(*) AS pass_rate,SUM(sc.score BETWEEN 70 AND 79)/COUNT(*) AS mid_rate,SUM(sc.score BETWEEN 80 AND 89)/COUNT(*) AS good_rate,SUM(sc.score >= 90)/COUNT(*) AS excellent_rate,COUNT(*) AS stu_count
FROM SC sc
JOIN Course c ON sc.CId = c.CId
GROUP BY c.CId, c.Cname
ORDER BY stu_count DESC, c.CId ASC;
解析:
- 聚合函数组合:
MAX
/MIN
/AVG
分别计算最高分、最低分、平均分; - 条件计数技巧:
SUM(条件)
中,条件为TRUE
时返回 1,FALSE
时返回 0,因此SUM(sc.score >= 60)
即 “及格人数”,除以总人数(COUNT(*)
)得到及格率。 - 排序逻辑:先按选课人数(
stu_count
)降序(关注热门课程),人数相同则按课程 ID 升序(保证稳定排序)。
18. 按各科平均成绩排序(保留名次空缺)
SELECT c.CId, c.Cname, t.avg_score,RANK() OVER (ORDER BY t.avg_score DESC) AS rank_num
FROM (SELECT CId, AVG(score) AS avg_scoreFROM SC GROUP BY CId
) t
JOIN Course c ON t.CId = c.CId;
解析:
- 窗口函数
RANK()
:用于排名,特点是 “保留名次空缺”—— 若有 n 个并列第 k 名,则下一名为 k+n(如两个第 1 名,下一名为第 3)。 - 排序依据:
ORDER BY t.avg_score DESC
按平均成绩从高到低排名,反映各科整体难度或学生掌握程度。
19. 按各科平均成绩排序(不保留名次空缺)
SELECT c.CId, c.Cname, t.avg_score,DENSE_RANK() OVER (ORDER BY t.avg_score DESC) AS rank_num
FROM (SELECT CId, AVG(score) AS avg_scoreFROM SC GROUP BY CId
) t
JOIN Course c ON t.CId = c.CId;
解析:
- 窗口函数
DENSE_RANK()
:与RANK()
的区别是 “不保留名次空缺”—— 若有并列第 k 名,下一名仍为 k+1(如两个第 1 名,下一名为第 2),适合需要连续名次的场景(如评选等级)。
20. 学生总成绩排名(保留名次空缺)
SELECT s.SId, s.Sname, t.total_score,RANK() OVER (ORDER BY t.total_score DESC) AS rank_num
FROM (SELECT SId, SUM(score) AS total_scoreFROM SC GROUP BY SId
) t
JOIN Student s ON s.SId = t.SId;
解析:
- 总成绩计算:
SUM(score)
累加学生所有课程成绩得到总分(total_score
)。 - 排名逻辑:
RANK()
按总分降序排名,保留空缺(如两个满分学生并列第 1,下一名为第 3),适合需要严格区分名次的场景(如奖学金评定)。
21. 学生总成绩排名(不保留名次空缺)
SELECT s.SId, s.Sname, t.total_score,DENSE_RANK() OVER (ORDER BY t.total_score DESC) AS rank_num
FROM (SELECT SId, SUM(score) AS total_scoreFROM SC GROUP BY SId
) t
JOIN Student s ON s.SId = t.SId;
解析:
- 核心差异:使用
DENSE_RANK()
确保名次连续,即使有并列分数,也不会跳过名次(如两个满分学生并列第 1,下一名为第 2),适合需要分组统计(如 “前 10%”“前 20%”)的场景。
22. 各科成绩各分数段人数
SELECT c.CId, c.Cname,SUM(score BETWEEN 85 AND 100) AS '[100-85]',SUM(score BETWEEN 70 AND 84) AS '[85-70]',SUM(score BETWEEN 60 AND 69) AS '[70-60]',SUM(score < 60) AS '[60-0]',ROUND(SUM(score >= 85)/COUNT(*)*100,2) AS pct_85_100
FROM SC
JOIN Course c ON SC.CId = c.CId
GROUP BY c.CId, c.Cname;
解析:
- 分数段划分:
BETWEEN a AND b
包含 a 和 b,精准划分区间(如85-100
含 85 和 100);score < 60
直接筛选不及格。 - 百分比计算:
ROUND(...,2)
保留两位小数,SUM(score >=85)/COUNT(*)*100
计算高分段人数占比,反映课程难度(如高分段占比低可能课程较难)。
23. 各科成绩前三名
SELECT *
FROM (SELECT sc.*, RANK() OVER (PARTITION BY CId ORDER BY score DESC) AS rFROM SC sc
) t
WHERE r <= 3;
解析:
- 窗口函数分区:
PARTITION BY CId
按课程分组,确保每个课程单独排名(而非所有课程混合排名)。 - 排名筛选:
RANK() OVER (...) AS r
按分数降序为每个课程的成绩排名,外层WHERE r <=3
取前三名(含并列,如某课程有 4 个并列第 3,则均会被选中)。
24. 每门课程选修人数
SELECT CId, COUNT(*) AS stu_count
FROM SC GROUP BY CId;
解析:
- 简单分组统计:按课程 ID(
CId
)分组,COUNT(*)
统计每个组的记录数(即选修该课程的学生数),直接反映课程的热门程度。
25. 只选修两门课程的学生
SELECT s.SId, s.Sname
FROM Student s
JOIN SC sc ON s.SId = sc.SId
GROUP BY s.SId, s.Sname
HAVING COUNT(DISTINCT CId) = 2;
解析:
- 去重计数:
COUNT(DISTINCT CId)
统计学生选修的不同课程数量(排除重复选同一门课的情况),HAVING
筛选出数量等于 2 的学生,即 “只选修两门课”。
26. 男生、女生人数
SELECT Ssex, COUNT(*) AS cnt
FROM Student GROUP BY Ssex;
解析:
- 按性别分组:
GROUP BY Ssex
将学生分为 “男”“女” 两组(假设Ssex
只有这两个值),COUNT(*)
分别统计每组人数,用于性别比例分析。
27. 名字中含有「风」的学生信息
SELECT * FROM Student WHERE Sname LIKE '%风%';
解析:
- 通配符灵活匹配:
%风%
中%
匹配任意字符(包括 0 个),表示 “姓名中任意位置包含‘风’字”(如 “林风”“风子”“李风明” 均会被匹配)。
28. 同名同性学生名单及人数
SELECT Sname, Ssex, COUNT(*) AS cnt
FROM Student
GROUP BY Sname, Ssex
HAVING COUNT(*) > 1;
解析:
- 联合分组:
GROUP BY Sname, Ssex
确保 “同名且同性” 才会被分为一组(避免 “同名不同性” 被误判)。 - 筛选重复:
HAVING COUNT(*) > 1
只保留出现两次及以上的同名同性组合,方便排查重复信息或统计常见姓名。
29. 1990 年出生的学生
SELECT * FROM Student
WHERE YEAR(Sage) = 1990;
解析:
- 日期函数
YEAR()
:从生日字段(Sage
,假设为日期类型)中提取年份,与 1990 对比,精准筛选 1990 年出生的学生。
30. 每门课程平均成绩降序,平均相同按课程号升序
SELECT CId, AVG(score) AS avg_score
FROM SC
GROUP BY CId
ORDER BY avg_score DESC, CId ASC;
解析:
- 排序优先级:
ORDER BY
先按avg_score DESC
(平均成绩从高到低),若平均分相同,再按CId ASC
(课程号从小到大),确保排序结果唯一、可预期。
31. 平均成绩 ≥ 85 的学生
SELECT s.SId, s.Sname, AVG(score) AS avg_score
FROM Student s
JOIN SC sc ON s.SId = sc.SId
GROUP BY s.SId, s.Sname
HAVING AVG(score) >= 85;
解析:
- 高分学生筛选:按学生分组计算平均分,
HAVING
筛选出平均分≥85 的学生,用于识别优等生或推荐奖学金候选人。
32. 数学分数 < 60 的学生姓名和分数
SELECT s.Sname, sc.score
FROM SC sc
JOIN Student s ON sc.SId = s.SId
JOIN Course c ON sc.CId = c.CId
WHERE c.Cname = '数学' AND sc.score < 60;
解析:
- 多表关联定位:通过
SC → Course
关联找到 “数学” 课程(c.Cname = '数学'
),再筛选分数 < 60 的记录,最终关联学生表得到姓名,针对性查看数学不及格的学生。
33. 所有学生的课程及分数(含没选课的)
SELECT s.SId, s.Sname, c.Cname, sc.score
FROM Student s
CROSS JOIN Course c
LEFT JOIN SC sc ON s.SId = sc.SId AND c.CId = sc.CId;
解析:
- 笛卡尔积与左连接:
CROSS JOIN Course
生成 “所有学生 × 所有课程” 的组合(即每个学生与每门课程都匹配一次),再通过LEFT JOIN SC
关联实际选课记录,未选课的组合中score
为NULL
,确保结果包含所有学生的所有课程(无论是否选课)。
34. 任何一门课 ≥ 70 的学生姓名、课程名、分数
SELECT s.Sname, c.Cname, sc.score
FROM SC sc
JOIN Student s ON sc.SId = s.SId
JOIN Course c ON sc.CId = c.CId
WHERE sc.score >= 70;
解析:
- 简单条件筛选:
sc.score >=70
直接筛选出所有及格及以上的成绩记录,关联学生和课程表后,展示 “学生 - 课程 - 分数” 对应关系,用于分析及格情况。
35. 不及格课程
SELECT DISTINCT CId FROM SC WHERE score < 60;
解析:
- 去重筛选:
score <60
找出不及格记录,DISTINCT CId
确保每个有不及格记录的课程只显示一次,快速定位存在不及格情况的课程。
36. 01 课程成绩 ≥ 80 的学生
SELECT s.SId, s.Sname
FROM SC sc
JOIN Student s ON sc.SId = s.SId
WHERE sc.CId = '01' AND sc.score >= 80;
解析:
- 精准筛选:
sc.CId = '01'
定位 01 课程,sc.score >=80
筛选高分学生,关联学生表得到 ID 和姓名,用于识别该课程的优秀学生。
37. 每门课程的学生人数
SELECT CId, COUNT(*) AS stu_count
FROM SC GROUP BY CId;
解析:
- 与第 24 题完全一致,按课程分组统计选课人数,基础且常用的课程热度分析。
38. 张三老师所授课程成绩最高的学生(成绩唯一)
SELECT s.*, sc.score
FROM SC sc
JOIN Course c ON sc.CId = c.CId
JOIN Teacher t ON c.TId = t.TId
JOIN Student s ON sc.SId = s.SId
WHERE t.Tname = '张三'
ORDER BY sc.score DESC LIMIT 1;
解析:
- 排序取顶:通过关联找到张三老师的课程,按分数降序排序后,
LIMIT 1
取第一条记录(即最高分),适合成绩唯一的场景(若有并列最高分,仅取其中一条)。
39. 张三老师所授课程成绩最高的学生(成绩可能并列)
SELECT s.*, sc.score
FROM SC sc
JOIN Course c ON sc.CId = c.CId
JOIN Teacher t ON c.TId = t.TId
JOIN Student s ON sc.SId = s.SId
WHERE t.Tname = '张三'
AND sc.score = (SELECT MAX(sc2.score)FROM SC sc2JOIN Course c2 ON sc2.CId = c2.CIdJOIN Teacher t2 ON c2.TId = t2.TIdWHERE t2.Tname = '张三'
);
解析:
- 子查询取最高分:内层子查询先找出张三老师课程的最高分数(
MAX(sc2.score)
),外层筛选分数等于该最高分的学生,确保所有并列最高分的学生都被选中(解决第 38 题的并列问题)。
40. 不同课程成绩相同的学生
SELECT a.SId, a.CId, a.score
FROM SC a
JOIN SC b ON a.SId = b.SId AND a.CId <> b.CId AND a.score = b.score;
解析:
- 自连接条件:
a.SId = b.SId
确保同一学生,a.CId <> b.CId
确保不同课程,a.score = b.score
确保分数相同,三者结合找出 “同一学生在不同课程中成绩相同” 的记录。
41. 每门课程成绩最好的前两名
SELECT *
FROM (SELECT sc.*, RANK() OVER (PARTITION BY CId ORDER BY score DESC) AS rFROM SC sc
) t
WHERE r <= 2;
解析:
- 与第 23 题逻辑一致,按课程分区排名后取前两名,含并列情况(如某课程有 3 个并列第 2,则均会被选中)。
42. 学生数超过 5 的课程
SELECT CId, COUNT(*) AS stu_count
FROM SC
GROUP BY CId
HAVING COUNT(*) > 5;
解析:
- 分组筛选:按课程分组后,
HAVING COUNT(*) >5
筛选出选课人数超过 5 的课程,用于识别热门课程(需重点投入资源)。
43. 至少选修两门课程的学生
SELECT SId
FROM SC
GROUP BY SId
HAVING COUNT(DISTINCT CId) >= 2;
解析:
- 去重计数筛选:
COUNT(DISTINCT CId)
统计学生选修的不同课程数,HAVING >=2
找出至少选两门课的学生 ID,用于分析学生的选课活跃度。
44. 选修了全部课程的学生
SELECT SId
FROM SC
GROUP BY SId
HAVING COUNT(DISTINCT CId) = (SELECT COUNT(*) FROM Course);
解析:
- 全量匹配:子查询
SELECT COUNT(*) FROM Course
得到总课程数,若学生选修的不同课程数等于总课程数,则说明该学生选修了所有课程,用于识别 “全选课” 的学生。
45. 各学生年龄(按年份算)
SELECT SId, Sname, YEAR(CURDATE()) - YEAR(Sage) AS age
FROM Student;
解析:
- 简易年龄计算:
YEAR(CURDATE())
取当前年份,减去生日年份(YEAR(Sage)
)得到年龄,不考虑月份(如 1990 年 12 月和 1990 年 1 月出生的人会被算为同一年龄)。
46. 按生日精确计算年龄
SELECT SId, Sname,YEAR(CURDATE()) - YEAR(Sage) -(DATE_FORMAT(CURDATE(),'%m%d') < DATE_FORMAT(Sage,'%m%d')) AS age
FROM Student;
解析:
- 精确年龄逻辑:
- 基础年龄:当前年份 - 出生年份。
- 修正项:
DATE_FORMAT(..., '%m%d')
提取月日,若当前月日 < 出生月日(即生日尚未过),则年龄减 1((条件)
为TRUE
时返回 1,FALSE
返回 0)。
- 结果:精准计算年龄(如 2000 年 2 月 29 日出生,2023 年 3 月 1 日计算为 23 岁,2023 年 2 月 28 日计算为 22 岁)。
47. 本周过生日的学生
SELECT *
FROM Student
WHERE WEEK(Sage) = WEEK(CURDATE());
解析:
- 周数匹配:
WEEK(Sage)
取生日所在的周数,WEEK(CURDATE())
取当前周数,相等则为 “本周过生日”,注意:WEEK()
可能因数据库设置(如周一为周首还是周日)略有差异。
48. 下周过生日的学生
SELECT *
FROM Student
WHERE WEEK(Sage) = WEEK(CURDATE()) + 1;
解析:
- 周数加 1:当前周数加 1 即为下周,通过
WEEK(Sage)
匹配,筛选出下周过生日的学生,方便提前准备祝福。
49. 本月过生日的学生
SELECT *
FROM Student
WHERE MONTH(Sage) = MONTH(CURDATE());
解析:
- 月份匹配:
MONTH(Sage)
取生日月份,MONTH(CURDATE())
取当前月份,相等则为 “本月过生日”,简单直接的月度生日筛选。
50. 下月过生日的学生
SELECT *
FROM Student
WHERE MONTH(Sage) = MONTH(CURDATE() + INTERVAL 1 MONTH);
解析:
- 月份偏移:
CURDATE() + INTERVAL 1 MONTH
取 “当前日期加 1 个月”,MONTH()
提取其月份,与生日月份匹配,得到下月过生日的学生(自动处理 12 月加 1 月为次年 1 月的情况)。