
简介这份数据库实验报告面向高校数据库课程学习者聚焦数据统计查询与嵌套查询的实操训练帮助读者掌握SELECT语句、统计函数、连接查询及子查询的综合运用。文档以CPXS数据库为背景涵盖COUNT、SUM、MAX、MIN等函数统计、GROUP BY与HAVING分组筛选、INNER JOIN等多表连接以及子查询、派生表等嵌套查询的典型题型并配有实验目的、内容与思考总结便于对照练习与复盘。资源包内共1个doc文件大小约642KB结构完整可直接用于课程实验提交或自学参考。目前已有930人学习下载适合正在学习数据库查询语言、需要巩固嵌套查询与统计查询思路的学生和自学者使用。1. 从一份“数据库实验5嵌套查询.doc”说起嵌套查询到底在查什么如果你手头正躺着一份名为“数据库实验5嵌套查询.doc”的实验文档大概率你面对的是这样一道题用一条 SQL 把“成绩高于全班平均分的学生”“选修了某门课的所有人”“没有出现在选课表里的学生”这类问题查出来。它不像增删改查那样直白也不像连接查询那样一眼能看懂表之间的关系而是把一个查询的结果当成另一个查询的条件来用。这就是嵌套查询也叫子查询。它解决的核心问题是当筛选条件本身需要先算一遍才能得到时单层查询写不出来嵌套查询就能派上用场。适合谁正在做数据库课程实验的学生、刚接触 SQL 的开发者、以及写了几年 CRUD 但一遇到“比平均分高”“不在某集合里”就卡壳的工程师。热搜里“sql的连接查询和子查询”“select”“统计查询”这些词本质上都在问同一件事一条 SQL 到底能表达多复杂的逻辑。嵌套查询就是答案的一半。2. 嵌套查询的执行逻辑与三类写法先搞懂谁先跑2.1 子查询不是“先写先跑”执行顺序由位置决定很多人第一次写嵌套查询会翻车是因为脑子里默认“SQL 从上往下执行”。实际上数据库优化器关心的不是书写顺序而是子查询出现在哪里、能不能被改写。常见位置有三类WHERE 子句里的子查询作为过滤条件返回单值或一列值。FROM 子句里的子查询作为一张临时表必须给别名。SELECT 子句里的子查询作为一列的计算结果通常配合聚合函数。理解执行顺序最实用的判断方法是看子查询能不能“独立跑”。把子查询单独拎出来执行一次如果能得到结果那它大概率是非相关子查询数据库可以先算它再拿结果去过滤外层。如果子查询里引用了外层的列单独跑会报错那就是相关子查询外层每扫一行子查询就要跑一次。提示相关子查询在数据量大时性能会明显下降因为它是“逐行触发”的。实验里数据少感觉不到真实项目里要警惕。2.2 比较运算符、IN、EXISTS三种最常用的嵌套写法先建两张实验表后面所有例子都基于它们-- 学生表 CREATE TABLE student ( sno VARCHAR(10) PRIMARY KEY, -- 学号 sname VARCHAR(20), -- 姓名 sdept VARCHAR(20) -- 院系 ); -- 成绩表 CREATE TABLE score ( sno VARCHAR(10), -- 学号 cno VARCHAR(10), -- 课程号 grade DECIMAL(5,1) -- 成绩 );写法一比较运算符 聚合子查询。查成绩高于全体平均分的学生SELECT sno, cno, grade FROM score WHERE grade (SELECT AVG(grade) FROM score);这里的子查询返回单个值所以可以直接用。逻辑说明内层先算出平均分外层再逐行比较。参数上唯一要注意的是如果子查询可能返回多行用比较运算符就会报错必须换成IN或加LIMIT。写法二IN 多值子查询。查选修了“数据库”这门课的学生SELECT sno, sname FROM student WHERE sno IN ( SELECT sno FROM score WHERE cno C001 );IN适合子查询返回一列多行的场景。逻辑上等价于“把子查询结果当成一个集合外层判断是否属于这个集合”。参数说明IN后面的子查询只能返回一列返回多列会直接报错这是实验里最常见的翻车点之一。写法三EXISTS 相关子查询。查至少有一门课及格的学生SELECT sno, sname FROM student s WHERE EXISTS ( SELECT 1 FROM score sc WHERE sc.sno s.sno AND sc.grade 60 );EXISTS只关心子查询有没有返回行不关心返回什么所以写SELECT 1就够了。它的执行逻辑是外层每取一个学生就拿学号去子查询里找有没有及格记录找到就保留。这种写法在“存在性判断”场景下通常比IN更高效尤其是子查询表很大的时候。2.3 相关子查询和非相关子查询性能差异从哪来把上面三种写法按“是否引用外层列”分类类型是否引用外层列执行方式典型场景非相关子查询否子查询先独立执行一次比平均分高、IN 集合相关子查询是外层每行触发一次子查询EXISTS、逐行对比非相关子查询只跑一次结果可以缓存相关子查询理论上跑 N 次。实验数据只有几十行时两者没区别但换成十万行相关子查询可能就是秒级和分钟级的差距。我一般会先写EXISTS保证语义正确再用EXPLAIN看执行计划如果发现子查询被反复扫描就考虑改写成连接查询。3. 把嵌套查询落到实验报告里从建表到出结果的完整步骤3.1 建表与造数据实验环境的最小闭环实验文档通常只给题目不给数据。自己造数据有个好处边界情况可控。下面这段脚本建表并插入能覆盖“平均分”“无选课”“多门课”三种情况的数据-- 插入学生 INSERT INTO student VALUES (S001, 张三, 计算机), (S002, 李四, 计算机), (S003, 王五, 数学), (S004, 赵六, 数学); -- 赵六没有选课 -- 插入成绩 INSERT INTO score VALUES (S001, C001, 85.0), (S001, C002, 90.0), (S002, C001, 55.0), (S002, C002, 60.0), (S003, C001, 78.0);逻辑说明赵六故意不插入成绩用来验证“没有选课的学生”这类反连接查询。参数上grade用DECIMAL(5,1)而不是FLOAT避免平均分出现84.99999这种浮点误差实验报告里数字对不上时先检查字段类型。3.2 逐题拆解把自然语言翻译成嵌套结构实验题一般长这样“查询成绩高于该课程平均分的学生学号、课程号和成绩”。注意这里不是全体平均分而是每门课的平均分这就必须用相关子查询SELECT sno, cno, grade FROM score sc1 WHERE grade ( SELECT AVG(grade) FROM score sc2 WHERE sc2.cno sc1.cno -- 关键按课程号关联 );逻辑说明外层每取一条成绩记录内层就按这条记录的课程号算一次该课平均分再比较。参数上sc1和sc2是同一张表的两个别名必须区分开否则数据库不知道你引用的是外层还是内层。再比如“查询没有选修 C001 课程的学生”SELECT sno, sname FROM student WHERE sno NOT IN ( SELECT sno FROM score WHERE cno C001 );这里有个血泪经验如果子查询结果里包含NULLNOT IN会返回空结果。因为NULL参与比较的结果是未知整个条件就不成立。解决办法是用NOT EXISTS改写SELECT sno, sname FROM student s WHERE NOT EXISTS ( SELECT 1 FROM score sc WHERE sc.sno s.sno AND sc.cno C001 );3.3 用 EXPLAIN 验证子查询到底跑了几次实验报告里如果只贴 SQL 和结果说服力有限。加一步执行计划分析立刻拉开差距。以 MySQL 为例EXPLAIN SELECT sno, cno, grade FROM score sc1 WHERE grade ( SELECT AVG(grade) FROM score sc2 WHERE sc2.cno sc1.cno );看输出里的type和rows列。如果子查询被标记为DEPENDENT SUBQUERY说明它是相关子查询外层每行都会触发。如果数据量大可以考虑改写成连接 分组SELECT sc1.sno, sc1.cno, sc1.grade FROM score sc1 JOIN ( SELECT cno, AVG(grade) AS avg_grade FROM score GROUP BY cno ) t ON sc1.cno t.cno WHERE sc1.grade t.avg_grade;逻辑说明把相关子查询改成 FROM 子句里的派生表先按课程分组算好平均分再连接回原表。参数上派生表必须起别名这里是t否则报错。这种改写在大数据量下通常更快因为聚合只算了一次。4. 嵌套查询避坑5 个实验里最容易翻车的地方4.1 子查询返回多行却用了比较运算符现象执行WHERE grade (SELECT grade FROM score WHERE cnoC001)报错Subquery returns more than 1 row。原因、、这类比较运算符只接受单值而子查询返回了一列多行。解决要么在子查询里加聚合函数AVG、MAX要么把外层改成IN、ANY、ALL。实验里先确认子查询单独跑返回几行再决定用哪种。4.2 NOT IN 遇到 NULL 直接返回空结果现象WHERE sno NOT IN (SELECT sno FROM score)明明有学生没选课却查不出任何行。原因score表的sno列如果存在NULLNOT IN的比较结果全部为未知条件不成立。解决子查询里加WHERE sno IS NOT NULL或者直接用NOT EXISTS。这是 SQL 里最经典的“玄学”之一实验报告里写清楚能加分。4.3 相关子查询忘了写关联条件退化成笛卡尔积现象查询跑了几分钟没结果或者结果行数异常多。原因内层子查询引用了外层别名但 WHERE 里漏了关联条件导致每行都跟全表比较。解决检查子查询的 WHERE 是否包含内层表.列 外层表.列。用EXPLAIN看rows列如果数字接近两表行数乘积基本就是漏了关联。4.4 FROM 子句里的派生表没起别名现象SELECT * FROM (SELECT sno FROM score)报语法错误。原因FROM 后面的子查询必须有一个别名这是 SQL 标准要求MySQL、Oracle、PostgreSQL 都一样。解决加AS t或直接空格加别名。实验里如果用的是 SQL Server报错信息会更明确但本质相同。4.5 嵌套层数过深导致可读性和性能双输现象三层以上嵌套自己过两天都看不懂改一个条件牵一发动全身。原因把本可以用连接或 CTE 表达的逻辑硬塞进嵌套。解决超过两层就考虑用WITHCTE拆开或者改写成连接查询。实验题一般两层够用但真实项目里三层嵌套基本是维护噩梦。5. 从实验到实战嵌套查询的进阶用法与验证习惯实验做完真正的价值在于知道什么时候不该用嵌套查询。我自己的习惯是先写嵌套保证语义正确再用EXPLAIN看执行计划如果出现DEPENDENT SUBQUERY且外层行数大就改写成连接或 CTE。下面这个 CTE 写法可读性和性能通常都更好WITH course_avg AS ( SELECT cno, AVG(grade) AS avg_grade FROM score GROUP BY cno ) SELECT sc.sno, sc.cno, sc.grade FROM score sc JOIN course_avg ca ON sc.cno ca.cno WHERE sc.grade ca.avg_grade;逻辑说明WITH先把每门课的平均分算好存成临时结果集再和原表连接。参数上CTE 只在当前查询有效不会持久化适合一次性分析。相比相关子查询它把“逐行触发”变成了“一次聚合 一次连接”数据量越大优势越明显。验证方法上我一般会做两件事一是用COUNT(*)对比嵌套写法和连接写法的结果行数是否一致二是故意插入边界数据比如成绩为NULL、学号不存在于学生表看两种写法是否都返回预期结果。这一步能提前暴露NOT IN和NULL的坑。还有一个容易被忽略的技巧在 SELECT 子句里用标量子查询做列计算。比如查每个学生的选课门数SELECT s.sno, s.sname, (SELECT COUNT(*) FROM score sc WHERE sc.sno s.sno) AS course_count FROM student s;这种写法在报表场景很常见但要注意子查询必须返回单值否则报错。如果某个学生没有选课COUNT(*)返回 0 而不是NULL这正是我们想要的。最后说个我自己的教训早年做实验时我把“查询没有选修 C001 的学生”写成了NOT IN数据里恰好没有NULL跑通了就交了上去。后来换了一批数据结果直接为空排查了半天才想起NULL的坑。从那以后凡是涉及“不在集合里”的判断我一律用NOT EXISTS再也没翻过车。嵌套查询不难难的是对边界条件的敬畏。希望帮到你。本文还有配套的精品资源点击获取