高阶子查询题目精炼
数据库子查询题集(10题)
以下题目基于典型的“学生-课程-成绩”数据库模式,并引入复杂场景、性能陷阱及高级SQL特性,旨在深入考察对子查询执行原理、优化策略及逻辑转换的理解。
| 题号 | 题目描述 | 考察核心 |
|---|---|---|
| 1 | 多层嵌套与聚合:查询“平均成绩高于全校学生平均成绩,且其最高单科成绩高于该科目平均分20分以上”的学生的学号和姓名。 | 多层聚合子查询、HAVING子句中的复杂条件。 |
| 2 | 相关子查询与极值:找出每门课程中,成绩不是最高也不是最低的学生名单(即排除每门课的最高分和最低分获得者)。 | 相关子查询、NOT IN与极值查询(MAX,MIN)的结合。 |
| 3 | EXISTS 与 三表关联:查询从未选修过“张老师”所授课程,但选修过所有“李老师”所授课程的学生信息。 | EXISTS,NOT EXISTS与全称量词的逻辑转换(通过双重否定或计数实现)。 |
| 4 | ALL/ANY 与除法运算:查询选修了计算机系开设的全部课程的学生学号。 | 使用NOT EXISTS模拟关系代数中的“除法”运算。 |
| 5 | 标量子查询与更新:将每位学生的总成绩更新到学生表的一个新增字段total_score中,要求使用单条UPDATE语句配合标量子查询完成。 | 在UPDATE的SET子句中使用相关标量子查询。 |
| 6 | FROM 子句中的子查询与窗口函数:查询每个班级中,总成绩排名前10%的学生。要求使用FROM子句中的子查询先计算排名和百分比,再筛选。 | 子查询生成派生表(Derived Table)并与窗口函数(RANK(),NTILE())结合。 |
| 7 | 条件子查询与 CASE 表达式:生成一份报告,列出所有学生。对于每个学生,如果其有任何一门课成绩低于60分,则显示“有不及格”;如果所有成绩都在85分以上,则显示“优秀”;否则显示“普通”。 | 在SELECT子句和CASE WHEN中使用EXISTS和聚合子查询。 |
| 8 | 横向连接 (LATERAL JOIN) 问题:对于每个学生,找出其成绩最高的两门课程的名称和分数。 | 使用LATERAL子查询(或特定数据库的等效语法)实现行间关联。 |
| 9 | 集合运算与子查询性能:查询本学期有成绩记录,但上学年没有任何成绩记录的学生。分别用NOT IN、NOT EXISTS和LEFT JOIN...IS NULL三种方法实现,并分析其执行计划可能存在的差异。 | 不同逻辑写法对数据库优化器(如He3DB的子连接上拉)的影响及性能对比。 |
| 10 | 递归子查询(CTE):给定一个“员工-经理”表(employee_id, manager_id, salary),找出每个员工的所有间接下属(不限层级)中,薪资最高的那位员工的薪资。 | 使用递归公共表表达式(CTE)遍历树形结构,并在外层查询中进行聚合。 |
部分难题的参考解答与深度解析
题目 2:相关子查询与极值
-- 方法:找出每门课成绩既不是最大值也不是最小值的学生 SELECT DISTINCT s.student_id, s.name, c.course_name, sc.score FROM student s JOIN score sc ON s.student_id = sc.student_id JOIN course c ON sc.course_id = c.course_id WHERE sc.score <> ( SELECT MAX(score) FROM score sc2 WHERE sc2.course_id = sc.course_id -- 相关子查询,找当前课程最高分 ) AND sc.score <> ( SELECT MIN(score) FROM score sc3 WHERE sc3.course_id = sc.course_id -- 相关子查询,找当前课程最低分 );解析:此查询为每个成绩行执行了两次相关子查询以获取当前课程的极值,可能产生性能问题。优化器可能尝试将其转换为JOIN,但相关性子查询限制了上拉优化。
题目 4:ALL/ANY 与除法运算(使用 NOT EXISTS 实现)
-- 查询选修了计算机系全部课程的学生 SELECT s.student_id FROM student s WHERE NOT EXISTS ( -- 找出计算机系开设的、该学生没选的课程 SELECT c.course_id FROM course c WHERE c.dept = '计算机系' AND NOT EXISTS ( SELECT 1 FROM score sc WHERE sc.student_id = s.student_id AND sc.course_id = c.course_id ) );解析:这是经典的关系除法问题的NOT EXISTS解法。它通过双重否定(“不存在一门计算机系的课是他没选的”)来表达全称量词。外层查询遍历学生,内层第一重NOT EXISTS检查是否有一门计算机系的课不在该学生的选课列表中(由最内层NOT EXISTS判断)。这种嵌套相关子查询是优化器的重点优化对象,在He3DB等数据库中可能会被重写为更高效的semijoin或antijoin。
题目 9:集合运算与性能分析
-- 方法1: NOT IN (需注意NULL值问题) SELECT student_id FROM score WHERE term = '2024-春' AND student_id NOT IN ( SELECT student_id FROM score WHERE term = '2023-秋' ); -- 方法2: NOT EXISTS (通常更优) SELECT s.student_id FROM score s WHERE s.term = '2024-春' AND NOT EXISTS ( SELECT 1 FROM score s2 WHERE s2.term = '2023-秋' AND s2.student_id = s.student_id ); -- 方法3: LEFT JOIN ... IS NULL SELECT DISTINCT s.student_id FROM score s LEFT JOIN score s_prev ON s.student_id = s_prev.student_id AND s_prev.term = '2023-秋' WHERE s.term = '2024-春' AND s_prev.student_id IS NULL;性能分析:
NOT IN:如果子查询返回结果集包含NULL值,则整个NOT IN条件可能返回UNKNOWN(即无结果),这是逻辑陷阱。性能上,数据库可能将其实现为Hash Anti Join。NOT EXISTS:是表达此逻辑最清晰和安全的方式,通常能利用关联字段的索引。现代数据库优化器(如MySQL 8.0+、He3DB)擅长将NOT EXISTS重写为高效的antijoin。LEFT JOIN...IS NULL:这是一种更“过程化”的写法,明确指示数据库先进行连接操作。在拥有良好统计信息时,优化器可能生成与NOT EXISTS相似的执行计划。但在复杂情况下,它可能比NOT EXISTS产生更大的中间结果集。
核心优化点:数据库优化器在查询重写阶段,会尝试将子查询(特别是EXISTS/IN类)“上拉”为JOIN,以减少嵌套迭代。例如,He3DB会将满足条件的EXISTS_SUBLINK转换为semijoin,将NOT EXISTS转换为antijoin,从而可以利用高效的连接算法(如Hash Join)和并行执行。能否成功上拉取决于子查询是否“相关”(引用外层列)以及其复杂程度。
题目 10:递归子查询(CTE)
WITH RECURSIVE SubordinateTree AS ( -- 锚点:直接下属 SELECT employee_id, manager_id, salary FROM employee UNION ALL -- 递归:间接下属 SELECT st.employee_id, e.manager_id, e.salary FROM SubordinateTree st JOIN employee e ON st.manager_id = e.employee_id ) SELECT emp.employee_id, (SELECT MAX(salary) FROM SubordinateTree st WHERE st.employee_id = emp.employee_id) as max_subordinate_salary FROM employee emp;解析:此CTE先递归构建出每个员工的所有下属关系(包括间接)。外层查询通过一个标量子查询,从递归结果中找出每个员工下属的最高薪资。递归CTE是解决层次查询的强有力工具,但其性能依赖于递归深度和表的索引情况。
参考来源
- 大云海山数据库(He3DB) 内核分析-子查询优化
- PTA沈师数据库原理——DB(10)_SQL实验题
- MySQL查询之子查询详解:从执行原理到深度优化
- 数据库子查询
- 数据库子查询
- 数据仓库原理与实践课程指南
