简介:《SQL语句练习题及答案》是一份DOC练习题文档,面向正在学习数据库原理或SQL语法的高校学生、自学者及备考人员,用于夯实建表、增删改查与查询统计等核心技能。内容围绕School数据库中的Student、Course、SC三张表展开,系统覆盖建表与主码设置、数据插入与删除、属性修改与更新,以及单表查询、统计计算、多表连接、嵌套查询和相关子查询等典型场景。题目条件贴近课堂与考试常见要求,如按年龄排序、按10分制显示成绩、计算每门课的平均分与最高分、统计不及格人数、查询平均分前三名等;每题附有参考答案,便于即时对照。文件共1个DOC文档,大小仅43KB,轻量易用,可直接打开练习或打印。已有521人学习下载,无论是课程期末复习、SQL面试准备,还是日常练习巩固,都能从中获得系统训练。
1. 一份 .doc 里的 SQL 练习题,为什么比刷一百节视频更值钱
搜索“sql语句练习题及答案.doc”这个标题的人,多半已经在“看懂教程”和“写出 SQL”之间摔过跟头。跟着视频敲两行,当时觉得会了,关掉窗口再让写一条带 JOIN 的查询,又卡在原地。这个 .doc 文件的价值,恰恰不在那几十页纸,而在它给了一条能自我验证的路径:题目负责把你逼到写不出来,答案负责告诉你差在哪。对准备面试、刚转数仓、或者要带新人写数的从业者来说,这种“题 + 答案”的对照训练比刷视频课更贴近真实工作状态。真正的收获不是把答案背下来,而是把一道题从读题、拆解到验证的完整过程重复多遍,直到形成肌肉记忆。这篇就沿着“先归类题型、再建表练习、最后对照答案复盘”的顺序,把每一步怎么落地、坑在哪讲透。
2. 拿到练习题先别急着写:先看懂这 5 类必考题型
新手最容易犯的错误是拿到题就开写,写一半发现不会关联、不会分组,又回去翻语法。其实练习题翻来覆去就那么几个考点:单表查询、多表关联、分组聚合、子查询与 CTE、窗口函数与增删改。先判断题目在考哪一类,再决定用什么写法,准确率会明显提升。
2.1 单表查询:一切练习的地基
单表查询考察的是 SELECT、WHERE、ORDER BY、LIMIT 这些最基础的子句。它看起来最简单,却是后面所有练习的脚手架。大多数人在这一层翻车,不是不会写 SELECT,而是不知道 WHERE 的执行顺序在 SELECT 之前,所以不能在 WHERE 里引用 SELECT 里刚起的别名。
练习题最常见的一类是这样的:查学生表中某个年份以后出生、成绩大于某个值的学生,按年龄排序。
-- 单表查询:先过滤,再投影,最后排序 SELECT name, birth_year FROM students WHERE birth_year >= 2000 AND score > 80 ORDER BY birth_year DESC;这段代码的逻辑是先用 WHERE 过滤不满足条件的行,再做投影,最后排序。两个条件中间用 AND 连接,对应题面里的“且”。练习时最容易错的地方是边界值:题目写“2000 年以后”,到底包不包括 2000 年?写“大于 80”,那 80 分整算不算?动笔前先把这些边界词圈出来,写 WHERE 时才能一次到位。单表题的另一个常见变体是分页,比如“按成绩从高到低取前 5 名”,对应 ORDER BY + LIMIT。建议每道题动手前,先把题面里的过滤条件圈出来,再想 SELECT 要保留哪些列,最后才排序和分页。
2.2 多表关联:JOIN 是区分“背过”和“会写”的分水岭
多表关联是面试和工作中真正的分水岭。它考的不只是 LEFT JOIN 和 INNER JOIN 的语法区别,还包括:关联键有没有重复、会不会造成结果集膨胀、用 ON 过滤和用 WHERE 过滤有什么不同。三道题就能筛选出是“背过语法”还是“真会写”。
常见题型是查每个学生的选课信息和对应成绩,一张学生表,一张选课表,一张课程表。
-- 三表关联,注意关联顺序:学生 -> 选课 -> 课程 SELECT s.name, c.course_name, sc.score FROM students s LEFT JOIN student_courses sc ON s.student_id = sc.student_id LEFT JOIN courses c ON sc.course_id = c.course_id WHERE sc.score < 60 OR sc.score IS NULL;这里 LEFT JOIN 的顺序有讲究:先让学生和选课记录关联,再用选课记录关联课程,一旦把顺序写反,比如直接用学生表关联课程表,就会产生笛卡尔积,结果行数爆炸。ON 后面只放关联条件,WHERE 后面才放业务过滤条件,这个习惯能帮你避免很多迷糊的报错。练关联题时,动笔前先在草稿上画出表之间的关系,确认主表和从表,再写 JOIN。如果某张表的关联键不唯一,结果会出现重复行,这也是需要重点检查的地方。
2.3 分组与聚合:GROUP BY 的边界感在哪里
分组聚合是练习题里出错率最高的一块。初学者分不清“分组前过滤”和“分组后过滤”,于是把 WHERE 和 HAVING 用反;稍微进阶一点的,搞不清 SELECT 里哪些列必须出现在 GROUP BY 里,跑一条错一条。
典型题目:统计每门课程的选课人数,并筛出选课人数超过 5 人的课程。
-- 分组后过滤必须用 HAVING SELECT course_id, COUNT(*) AS student_cnt FROM student_courses GROUP BY course_id HAVING COUNT(*) > 5;这里有两个关键点。第一,WHERE 在分组前执行,HAVING 在分组后执行,所以“选课人数超过 5 人”这个条件只能放 HAVING;如果题目改成“统计 2020 年以后的选课人数”,这个时间条件才放 WHERE。第二,SELECT 里出现的非聚合列必须出现在 GROUP BY 里,这是标准 SQL 的硬性要求,也是练习文档里最爱埋的坑。练分组题时,建议把聚合函数圈出来、把分组列写在最前面,能少走很多弯路。分组题的变体是“每个班级每门课的平均分”,这就是多列分组,GROUP BY 后跟两个字段即可。
2.4 子查询与 CTE:把复杂问题拆成小问题的两种姿势
复杂题往往不是考一个技巧,而是考多个条件嵌套。子查询和 CTE 就是用来拆解复杂问题的工具。它们的区别在于:子查询直接嵌在 WHERE 或 FROM 里,CTE 则先声明一段临时结果集再复用。后者在可读性和排错上更友好。
典型题:查出成绩高于各科平均分的学生名单。难点在于“每科平均分”要先算出来,再拿每一条成绩去比,一步写成很困难。
-- CTE 先把平均分算出来,再 JOIN 主查询 WITH avg_scores AS ( SELECT course_id, AVG(score) AS avg_score FROM scores GROUP BY course_id ) SELECT s.name, sc.course_id, sc.score FROM student_courses sc JOIN students s ON s.student_id = sc.student_id JOIN avg_scores a ON a.course_id = sc.course_id WHERE sc.score > a.avg_score;CTE 的作用是“把算平均分”这个子问题独立出来,后面直接 JOIN 这段临时结果集,每一段都能单独跑通验证。练题时如果一段 SQL 超过二十行,优先考虑拆 CTE,而不是在一层查询里堆条件。这类题的另一种常见写法是相关子查询,在 WHERE 里对每一行重新算一次平均值,逻辑一样,但可读性和性能都不如 CTE。练习题里凡是出现“比平均、比最大、比最小”这类字眼,几乎都能用这个思路解。
2.5 窗口函数与更新删除:进阶题到底在考什么
窗口函数是练习文档里的进阶考点。它和 GROUP BY 最大的区别是:GROUP BY 会把多行合并成一行,窗口函数则保留每一行,同时在行集上做计算。经典题是查每个班级里成绩排名前三的学生。
-- 窗口函数:先分组排名,再过滤前三 SELECT name, class_id, score FROM ( SELECT name, class_id, score, ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) AS rn FROM students_scores ) t WHERE rn <= 3;PARTITION BY 是给窗口划范围,相当于每个班级独立排名;ORDER BY 决定排名顺序;ROW_NUMBER 遇到并列成绩也会给出不同序号。如果题目要求并列名次,要换成 RANK 或 DENSE_RANK。练习时只问自己一个问题:合并还是不合并。需要保留明细行、又要做排名或累计的,用窗口函数;只需要汇总结果的,用 GROUP BY。绝大多数进阶练习题靠这两类就能覆盖。
除查询外,文档里通常还会带几道 UPDATE 和 DELETE 题。它们的坑在于:很多人忘记先 SELECT 预览影响行数,直接执行,结果把整张表改了。练习这类题时,第一步永远是先写 SELECT 查出将要被影响的行,确认无误后再改成 UPDATE 或 DELETE 重新执行。
3. 先手写再上机:一套能复现的 SQL 练习闭环
有了题型认知,接下来是“怎么练”的问题。我推荐两遍法:先手写,再上机验证。很多人打开数据库边写边试,结果数据库一次次告诉你答案,你自己的推理过程反而没有被训练。正确做法是模拟考试状态,手写完成后,再逐步上机核验。
3.1 准备一套可连续使用的练习环境
环境准备只需要三样:一个能跑的数据库服务、一个能看结果的客户端、一套能反复重建的样例数据。本地装一个你熟悉的关系型数据库即可,开源的商业的都行。重点是建一个专门的练习库,和业务库彻底分开,不要在核心库上练题,这是血泪经验。
-- 建练习库,按需指定字符集 CREATE DATABASE practice; -- 使用练习库 USE practice;字符集建议选支持中文的 UTF8 系列,练习数据里学生姓名、课程名一般都会带中文,不配好字符集后面插入数据会出现乱码,查错浪费大量时间。建完库之后,把每次练习的建表脚本存成单独文件,跑挂了就直接重建,不用手动清理。这个习惯在做练习题阶段就能避免很多连带事故。
3.2 建库建表:把题面还原成可信的测试数据
练习题文档里的题目通常只有一两句话,但很多坑藏在边界条件里,比如成绩为空、重复选课、班级人数为零。这时需要自己造数据来验证答案。建表不必一次到位,但表结构要能支撑多道题复用。
-- 还原题目场景的最小表结构 CREATE TABLE students ( student_id INT PRIMARY KEY, name VARCHAR(50) NOT NULL, birth_year INT, class_id INT ); CREATE TABLE scores ( student_id INT, course_id INT, score DECIMAL(5, 2), PRIMARY KEY (student_id, course_id) );这里把复合主键放在 scores 表上是有意的:一个学生同一门课只有一条成绩,这能避免练 JOIN 时出现结果集膨胀。如果题目场景允许同一学生补考两次,再把主键去掉改成流水号即可。练习时测试数据要比题目给的样例多一倍,特别是补上 NULL 值、空字符串、重复记录,这几个边界条件决定你的答案经不经得起验证。
-- 插入少量可控的测试数据 INSERT INTO students (student_id, name, birth_year, class_id) VALUES (1, '张三', 1999, 101), (2, '李四', 2001, 101), (3, '王五', 2000, 102), (4, '赵六', NULL, 102);注意赵六的出生年故意没填,这就是练习里的隐藏边界。凡是题目里可能出现 NULL 的情况,你的答案必须明确自己怎么处理它:是保留、过滤还是当成默认值。练题前先把这些边界数据造出来,等于给标准答案做了一次压力测试,很多你以为正确的写法在这组数据上一跑就露馅。
3.3 两遍法:手写查漏、上机验证
完整流程是三步:读题、手写、验证。读题时把题目里每个业务词翻译成 SQL 关键词,比如“每门课程”翻译成 GROUP BY course_id,“高于平均”翻译成 HAVING 或子查询。先在纸上写出关键子句的骨架,再补列名和条件。
手写阶段,不看任何参考资料,按考试状态写完整条 SQL。很多人习惯边翻笔记边写,这样练的其实是检索能力,不是写 SQL 的能力。手写时卡住,是好事,说明这里有个知识点没内化。把卡住的位置记在题目旁边,再带着疑问去查资料,记忆深度比直接看笔记高得多。
上机验证阶段,把自己写的 SQL 和标准答案分别跑一遍,不要只比最终结果,还要对比两个结果的集合是否完全一致。同一个查询如果两边顺序不同,可以先都加上 ORDER BY 再对比。这一步能直接暴露你遗漏的条件或多余的限制。注意,结果行数一致也不一定等价,还要比较具体列值,尤其是 NULL 出现的位置。
3.4 用 EXPLAIN 验证答案:你以为对了,数据库未必这么走
练习题往往不要求性能,但真实场景要求。因此,把答案写对只是第一步,学会看执行计划,才是区分熟练和初学的重要节点。做法是自己写的查询前面加上 EXPLAIN,观察扫描方式和预估行数。
EXPLAIN SELECT s.name, sc.score FROM student_courses sc JOIN students s ON s.student_id = sc.student_id WHERE sc.course_id = 1;输出结果里有几个信息值得关注。扫描类型如果是全表扫描且预估行数很大,就该检查 WHERE 列上是不是缺索引;关联顺序如果和你的预期相反,说明优化器判断另一张表作为驱动表更划算。练习阶段不必追求绝对最优,但要能看出明显的低效写法。给 WHERE 列建一个索引再跑一次,观察预估行数和扫描类型的变化,就能直观感受到索引的作用。练习题的数据量小,怎么跑都很快,这恰恰会掩盖性能问题。一个实用的习惯是:每做完一道题,顺手跑一次 EXPLAIN,把它当成答案的一部分,以后再接触真实业务数据时,至少不会两眼一抹黑。
4. 标准答案不是用来抄的:用参考答案做一次有效复盘
练习文档最有价值的部分就是答案。很多人对完答案发现“结果一样”,就直接过掉,这是极大的浪费。结果一样不等于写法一样,更不等于在任何数据集上都等价。一份参考答案的价值,是提供一条更简洁、更健壮的思考路径。
4.1 答案的三种风格:一种写法 vs 多种写法
翻开一组练习题答案,你会发现同一个题往往有多套写法:一套用 IN 子查询,一套用 EXISTS,还有一套用 JOIN。它们结果等价,却对应不同的思维习惯。以“查出没选任何课程的学生”为例:
-- 风格一:NOT IN 子查询 SELECT * FROM students WHERE student_id NOT IN (SELECT student_id FROM student_courses); -- 风格二:LEFT JOIN 补空 SELECT s.* FROM students s LEFT JOIN student_courses sc ON s.student_id = sc.student_id WHERE sc.student_id IS NULL;风格一直接按题目字面翻译,好懂;风格二把“没选课”翻译成“关联后没有匹配行”,需要多绕一层。两种写法在大多数数据库上结果一致,但 NOT IN 遇到子查询结果包含 NULL 时,会返回空结果,而 LEFT JOIN 版本不受影响。这就是复盘不能只看结果的原因。把你的答案和标准答案的每条子句逐一对照,思考作者为什么选这种写法,是在规避边界,还是在追求可读性。练题时还可以刻意把每道题写两遍:先按直觉写,再换一种思路写一遍,解法视野就是这样打开的。
4.2 把标准答案和自己的 SQL 放在一起 diff:怎么读差异
上机验证时,把两边结果都加上 ORDER BY,再逐行对比。对比维度至少有四个:结果集行数、列名和列顺序、NULL 处理方式、重复行处理方式。
结果集行数不同最常见,说明两个查询的过滤条件有差异。比如你写了 WHERE score > 60,标准答案写的是 WHERE score >= 60,而题目给的样例数据里恰好没有 60 分的记录,两边结果看不出差别,可真实数据里会差出好几行。这就是练习数据要故意构造边界成绩的原因。
-- 边界成绩 60 分:用同一组数据验证两段查询 SELECT student_id, score FROM scores WHERE score > 60; SELECT student_id, score FROM scores WHERE score >= 60;这样一组对照非常直观。练习题文档的答案通常基于一套规则,不会告诉你有没有边界数据,当你自己造的测试数据覆盖到 60、NULL、空班级时,就能提前发现这种差异。复盘时看到差异,不要急着改答案,先回题面文字里找依据:题目说“大于”,60 分就不算;题目说“不低于”,60 分才算。一切以题面语义为准,不以上机结果的“碰巧一致”为准。
4.3 从“出结果”到“接近标准”:判自己答案的四个维度
给自己判分时,我习惯用四个维度:可读性、健壮性、性能和语法规范。可读性看你是否用了有意义的别名、是否缩进对齐、是否用 CTE 拆分复杂逻辑;健壮性看 NULL、重复、空表是否都有明确处理;性能看执行计划的扫描类型和预估开销;语法规范看是否用了过时写法,比如把多条 OR 拼在 WHERE 里而不写 IN。
| 维度 | 检查点 | 反面示例 |
|---|---|---|
| 可读性 | 别名清晰、缩进一致 | a.id、b.id 满天飞 |
| 健壮性 | NULL 和重复值有处理 | 直接 NOT IN 不校验 NULL |
| 性能 | 执行计划无明显全表扫描 | 大表 WHERE 列无索引 |
| 规范 | 不写过时语法 | 多个 JOIN 用逗号拼在 WHERE 里 |
这四个维度全部达标,一道题才算真正吃透。练习题文档里的标准答案未必四个维度全优,但它是你的比较基准,复盘时每个维度写一行笔记,比抄十遍答案有用得多。如果你发现自己的答案在某个维度上优于参考答案,比如可读性更好或性能更优,也应该记录原因,这能帮你建立自己的评判标准。
5. SQL 练习题避坑指南:5 个最典型的丢分点与排查方法
再把练习中最容易翻车的几个点单拎出来,每一类都是实际练习中反复出现的,按现象、原因、解决的顺序写,方便对号入座。
5.1 练习题里最常见的三处“假结果”
现象:跑出来的结果和标准答案一样,但换一组数据就明显不对。原因通常是测试数据过少,掩盖了 NULL 和空字符串的差异。比如用 IS NULL 判断一个字段,但表里实际存的是空字符串,两条记录在界面上看着都是空,SQL 处理方式完全不同。解决:在测试数据里故意加入 NULL、空字符串、重复记录,重新跑两边结果做对比。这一步能提前暴露大多数边界问题。
现象:WHERE 和 HAVING 混用,导致过滤范围错位。具体表现是分组某条件后,结果里混进了不该出现的分组。原因是对分组前后执行顺序不敏感,把分组后的条件写进了 WHERE,而 WHERE 在分组前已经执行完。解决:把每个条件翻译成“分组前”还是“分组后”,分组后的条件一律放在 HAVING 里。这样一分类,基本不会再错。
现象:关联表后行数暴涨,一条学生记录对应出多条相同结果。原因是某个关联键不是唯一键,JOIN 时产生了重复匹配。解决:先分别查两个表的关联键是否有重复,确认唯一后再写 JOIN。练习阶段就把这个校验动作养成习惯,真实业务里能少踩不少坑。
5.2 同一道题在不同数据库里结果不一致
现象:一段 SQL 在自己的环境里跑得好好的,换另一个数据库结果不同,甚至直接报错。原因:SQL 方言差异,包括字符串截断规则、NULL 排序位置、保留字处理方式。比如某些数据库里 order 是保留字,直接当列名用就会报错。解决:写练习时先确认题目面向哪类数据库,答案尽量贴近标准 SQL。字段名取 order、group、desc 这类保留字时,用反引号或双引号包裹,或者干脆建表时就换成非保留字,省得后面对答案时被语法问题干扰。
这一类问题还容易出现在窗口函数的写法上。不同数据库对 ROW_NUMBER 的写法大体一致,但 RANK 和 DENSE_RANK 的并列处理略有差异。排查时优先看执行计划或错误提示,它们一般会明确指到具体的语法位置。把这类差异记录在练习笔记里,比硬背语法更有用。
5.3 参考答案本身也有问题
现象:某道题的标准答案跑出来为空,而你自己写的答案有数据,反复检查后觉得自己的答案更符合题目要求。原因:这类 .doc 练习文档大多是手动整理的,版本旧,答案没跟上题目改动,或录入时出现笔误。解决:以题目文字为准,答案只作参考。遇到明显可疑的答案,先自己造数据验证逻辑,验证能说通就以自己的回答为准。拿到文档后先整体扫一遍,看有没有“题面和答案不匹配”的迹象,别到对答案时才被发现。
另一种常见情况是两套写法结果集一致,但标准答案的语义和你理解得不一样。比如题目问“未通过的学生”,标准答案理解为 fail 字段为 1,而你理解为成绩低于 60,这两者可能指向不同的人。这时候不要纠结对错,回到题面确认业务定义。练习题的目的不是全对,而是锻炼自己对业务条件的敏感度,这一点在被答案卡住时特别值得坚持。
6. 从练题到能答题:3 个把练习题吃透的进阶技巧
练习题刷完一轮之后,很容易陷入“答案都对但换个场景又不会”的循环。我用了三个技巧把练习阶段的成果转成真实可用的能力。
6.1 把每道练习题当做一个需求文档来重述
动手前先写一句业务说明,回答三个问题:输入是什么、输出是什么、有哪些边界条件。把这句话以注释形式写在 SQL 上方,比如“查每个班级成绩前三的学生,成绩并列时都算”。这道题就从一个模糊句子变成了明确的实现方案,你会自然想到 SELECT 班级和姓名、按成绩排序、用窗口函数保留明细行。三个问题写不出来时,说明题还没读懂,这时候不要急着写代码。
6.2 反向出题,用同一份数据验证两种理解
从你造好的测试数据里找特殊值,比如 60 分边界、NULL 出生年、重复选课记录,先手工推出“这条查询应该返回什么”,再写 SQL 验证。如果手推结果和 SQL 输出一致,说明这条语句真的在按你的理解执行;如果不一致,就去查执行顺序或函数细节。这个方法比多做十道新题更有效,因为它逼着你解释每一个子句的语义。练习文档里的标准答案只覆盖正确答案,不会覆盖你对边界条件的理解,反向出题正好补齐这一块。
6.3 用三种方式重写同一道题,对比差异
同一道多表题,分别用 JOIN、子查询、窗口函数实现,对比结果集和可读性。这个习惯能帮你检验一个关键认知:它们各自在什么场景下会失效。我在带人练习时见过不少例子,只背 JOIN 的同学遇到“取分组后第一条记录”会愣住,而写过窗口函数的人会多一种解法。多写一遍不是重复劳动,是在给未来的自己备选方案。
最后说句实在话,练习文档最重要的不是看完,而是“练完”。我自己也经历过对完答案就翻篇的阶段,后来发现那些顺手抄下来的答案,换到真实需求里根本调不动。真正的收获来自每一次手写卡壳和每一次边界验证。希望这篇能把你的练习路径理顺一点,少踩几个我已经踩过的坑,祝练题顺利。
本文还有配套的精品资源,点击获取