想认真学 MySQL,但不知道该从哪里下手,这可能是很多人初学 SQL 时最大的困扰。网上教程一篇接一篇,可真到了自己上手建表、查询、统计数据的时候,却发现连题目都读不明白。今天想跟你分享一份我整理并反复打磨过的“MySQL SQL 100道基础练习题”全套练习方案,这套题我不仅自己带新人用过很多轮,也把它放进过公司内部培训里,只要老老实实刷完,SQL 基本功基本能稳扎稳打。你不需要任何编程基础,只要电脑上装好了 MySQL,能敲键盘,就能按着这套思路往下走。
这套题面向的是刚学会 SELECT * FROM 表名,但一遇到多表查询、分组统计就发懵的初学者。如果你已经能熟练写各种复杂的 JOIN 和子查询,那这套题对你的作用会打折扣,但用来查漏补缺也很快。整套题一共 100 道,全部手写完成大概需要一周到十天,每天抽一两个小时,刷完后,日常工作中的取数需求、报表统计需求都能自己应付,连面试里那种基础的 SQL 笔试题也基本能过关。我先把整套题的设计思路拆给你看,再挑重点题型详细讲讲怎么解、怎么避坑。
1. 这套题到底在练什么:100 道题的分类逻辑
1.1 从建表到窗口函数:一个完整的 SQL 能力梯度
你一定在收藏夹里见过不少“SQL 面试题合集”,但它们大多东拼西凑,今天考个 GROUP BY,明天跳到一个巨复杂的存储过程,练得人一头雾水。我在设计这套 100 题的时候,把 SQL 学习分成了 8 个能力模块,每个模块对应一批题目,难度从 L1 到 L4 慢慢爬升。
具体分布是这样的:
| 模块 | 题号范围 | 考察重点 | 难度 |
|---|---|---|---|
| 建表与约束 | 01-10 | 数据类型、主键、外键、唯一约束、默认值 | L1 |
| 数据增删改 | 11-20 | INSERT、UPDATE、DELETE、事务基础 | L1 |
| 单表查询基础 | 21-35 | SELECT 语法、WHERE、运算符、模糊查询 | L1-L2 |
| 排序与分页 | 36-42 | ORDER BY、LIMIT、TOP-N 问题 | L2 |
| 聚合函数与分组 | 43-58 | COUNT、SUM、AVG、MAX、MIN、GROUP BY、HAVING | L2 |
| 多表连接 | 59-74 | INNER JOIN、LEFT JOIN、RIGHT JOIN、自连接 | L3 |
| 子查询与 EXISTS | 75-88 | 标量、列、行子查询,IN 与 EXISTS 的转换 | L3 |
| 日期与字符串处理 | 89-95 | DATE_FORMAT、DATEDIFF、CONCAT、SUBSTRING 等 | L3 |
| 窗口函数与综合应用 | 96-100 | ROW_NUMBER、RANK、LAG 等开窗函数 | L4 |
你注意看这个顺序,它不是随便排的。前 20 道题其实是在帮你“造数据”和“改数据”,不把表结构和基础数据搞定,后面 80 道题就没有操作对象。这也是我特别想强调的一点:很多人刷 SQL 题喜欢直接在在线网站上做,但那些网站把建表语句都封装好了,你根本体会不到字段类型怎么设计、约束有什么用。当你回到真实工作场景,第一步往往是“给我从库里把数据导出来”,这时候连表结构都看不懂,题刷再多也白搭。
1.2 为什么我不用现成题库,而要自己造一套题
市面上现成的 SQL 练习系统不是没有,像 LeetCode 的数据库题库、SQLZoo,我都带新人用过。它们的优点是题目判题精准,环境不用配,网页打开就能写。但实际用下来我发现一个致命问题:真实业务里不是所有需求都能抽成 LeetCode 那种“给定两张表求一个结果”的题。 LeetCode 的数据集非常干净,每个 id 都是连续自增的,没有重复值,没有 NULL,可真实项目里的表,光是一个用户表就可能有好几万条带脏数据的记录。你在 LeetCode 上练得再熟练,遇到“这个字段怎么一会儿是 string 一会儿是 number”的现实表,照样卡壳。
所以我在这套 100 题里故意埋了很多“脏”情况。比如某一道题里,订单表的用户 id 会存在 NULL,因为你不能假设每个下单用户都注册了账号;比如有重复记录需要去重统计;比如日期字段有的是 DATE 类型,有的存的是字符串。把这些场景揉进练习题,你练的就不只是语法,而是面向真实数据的思维。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手前的基础环境准备:先有一张能跑的表
2.1 一套通用建表语句怎么设计
这套题的核心知识点都基于一个虚构的“校园管理系统”,包含学生表、课程表、选课表三张核心表,外加一张教师表和成绩明细表。选这个场景是因为它足够简单,每个字段的含义一眼能看懂,不牵扯业务背景,适合专心练语法。
我建议你不要直接拿网上的现成库,而是自己把建表语句敲一遍。原因有两个:第一,敲 CREATE TABLE 的过程本身就是练习;第二,你能根据自己的理解决定字段类型和长度,这样后面做题时才会真正去想“为什么这个字段要设成 DECIMAL(5,2) 而不是 FLOAT”。
sql复制-- 学生表
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '学生ID',
stu_no VARCHAR(20) NOT NULL UNIQUE COMMENT '学号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
gender CHAR(1) DEFAULT 'M' COMMENT '性别 M/F',
age INT COMMENT '年龄',
class_name VARCHAR(50) COMMENT '班级',
enroll_date DATE COMMENT '入学日期'
);
这里有几个设计点新手容易忽略。stu_no 是学号,虽然也是唯一值,但不能当主键,因为学号是业务字段,有可能会因为规则调整而变化,主键应该用无业务含义的自增 id。这个思想很多 SQL 新手一开始不理解,直到他遇到“学号前面要加个年份前缀”这种需求,才明白当初用学号当主键有多痛苦。
2.2 没有 MySQL 环境怎么办:一分钟自查清单
你可能会问:我还没装 MySQL,怎么开始?这里我把安装环节快速捋一遍。Windows 用户直接去官网下载 MySQL Community Server 的 .msi 安装包,安装时选择 Developer Default 就好。macOS 用户可以 brew install mysql,装完执行 mysql_secure_installation 设置密码。Linux 用户看发行版,Ubuntu 系用 apt install mysql-server。
装完后强烈建议你装一个图形化客户端,首推 Navicat(付费但功能强)或者 DBeaver(免费开源,我个人用得最多)。为什么建议装图形化客户端?不是说你以后工作不用命令行,而是在前期大量练习阶段,图形化能帮你快速查看表结构和数据分布,把精力集中在写 SQL 本身,而不是跟终端交互怄气。我见过太多新手好不容易把 SQL 写对了,结果在命令行里中文乱码、找不到库、退出不了 mysql 交互界面,白白消耗了大量热情。工具是拿来辅助学习的,怎么顺手怎么来。
真的。等你把库里这些表建好、造上几十条测试数据,你就有了一辈子都受用的练习场。接下来我们进入核心的题型拆解。
3. 题型拆解与核心 SQL 模式精讲
3.1 建表与约束题:DDL 才是地基
100 道题的前 10 道,全是建表题。这看起来简单,实际上门道很多。比如第 3 题:创建一个成绩表 score,包含学生 id、课程 id、成绩,要求一个学生一门课只能有一条成绩记录。你会怎么写?很多人的第一反应是把 id 设为主键,然后完事了。但正确的做法是什么?是把 (student_id, course_id) 这两个字段放到一起做联合主键或者加唯一约束。
sql复制CREATE TABLE score (
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL,
course_id INT NOT NULL,
score DECIMAL(5,2),
UNIQUE KEY uk_stu_course (student_id, course_id)
);
这就是“逻辑主键”和“物理主键”的区别。自增的 id 只是让每条记录有一个唯一标识,但业务上真正的唯一性是靠联合唯一约束来保证的。你在刷题时可能体会不到,但到了真实系统里,负责的业务表插入了一堆重复数据,老板让你清洗,你一查发现当初建表时就该加联合约束,那真是欲哭无泪。做这类题我建议你多想想:这条数据在业务上什么情况下会重复?如果会重复,要不要在数据库层面直接拦住?
3.2 单表查询与过滤:WHERE 逻辑的边界陷阱
单表查询是 SQL 里的“吃饭手艺”,但这部分题目想得高分一点也不容易。为什么?因为筛选条件稍不留神就会出边界问题。第 26 题“查出年龄在 18 到 22 岁之间的学生”,最自然的写法是 WHERE age BETWEEN 18 AND 22,注意这个语法是包含左右边界的。如果你换成了 age >= 18 AND age < 22,那 22 岁的学生就会被漏掉。看起来是小事,但在真实需求里,报表的截止值漏了还是多了,直接影响业务决策。
再举一个经典场景:查“姓张的学生”。WHERE name LIKE '张%' 就行了。但有道题升级了一下:查“名字里带'张'但不是姓张的学生”,比如“刘张伟”。这时候你写 WHERE name LIKE '%张%' AND name NOT LIKE '张%' 就会出问题,因为“张伟”这类姓张的人也符合“名字里带张”的条件。正确的过滤逻辑应该是 WHERE name LIKE '%张%' AND LEFT(name, 1) <> '张'。这种对边界条件的敏感度,不是靠背语法能解决的,只能靠多写多想多排查。
3.3 聚合与分组:HAVING 和 WHERE 到底怎么分
40 到 58 道题集中在聚合函数和分组上,这个模块是整套题的灵魂,也是新手最常折戟的地方。第 47 题:“统计每个班级的学生人数,只显示人数大于 30 的班级”。你要是写 WHERE COUNT(*) > 30 直接在数据库里报错,因为 WHERE 子句不能使用聚合函数。正确的方式是:
sql复制SELECT class_name, COUNT(*) AS cnt
FROM student
GROUP BY class_name
HAVING cnt > 30;
这个问题的核心是理解 SQL 的执行顺序:先 FROM 取表,再 WHERE 对原始行过滤,然后 GROUP BY 分组,接着执行聚合函数,最后 HAVING 对分组后的结果过滤。这个顺序我建议你手抄三遍贴屏幕旁边,因为它能解释你 90% 的报错。WHERE 是先筛数据再分组,HAVING 是先分组再筛组,两者执行时机完全不同。
还有一种很常见的情况:拿到一个需求,不知道用 WHERE 还是 HAVING。我的判断标准很简单——如果这个过滤条件依赖聚合结果,比如“数量大于 30”“平均分大于 80”,就一定要用 HAVING;如果只是针对原始字段的普通条件,比如“班级是 3 班”,就用 WHERE。这个标准在分组类题目里几乎能直接套用。
3.4 多表连接:从笛卡尔积到 JOIN 语义
进入第 59 题开始,就是真正的分水岭了,因为多表连接考察的不只是语法,而是对表之间关系的理解。很多人在这一阶段会写出一张“爆炸”的结果表:明明只想查 10 条记录,SELECT 出来却是 100 条。原因大概率是漏了连接条件,比如:
sql复制SELECT s.name, c.course_name
FROM student s, score sc, course c
WHERE s.id = sc.student_id;
你看,这里 student 和 score 关联了,但 score 和 course 之间没有关联条件,结果就是每个成绩记录都跟所有课程配对了一遍,行数直接膨胀成原来的好几倍。这就是经典的笛卡尔积错误。
正确写法是显式使用 JOIN:
sql复制SELECT s.name, c.course_name
FROM student s
JOIN score sc ON s.id = sc.student_id
JOIN course c ON sc.course_id = c.id;
我特别强调要用 JOIN ... ON 而不是在 WHERE 里写关联条件,除了可读性更好,更重要的是不容易漏条件。你每写一个 JOIN 就必须紧跟一个 ON,语法上强迫你把关联关系写全。而老式写法把所有表放在 FROM 后面用逗号隔开,关联条件全堆在 WHERE,少写一个条件也不报错,特别危险。
另外,我把 LEFT JOIN 单独拎出来出题是有原因的。好多开发写了几年 SQL,都没搞懂“LEFT JOIN 时条件放在 ON 还是 WHERE 里”的区别。比如我要查“所有学生的选课情况,没选课的也显示出来”,在 ON 后面加 sc.score IS NULL 和在 WHERE 后面加,结果完全不同。放在 ON 里是先连接再过滤右表,学生记录会保留;放到 WHERE 里是连接完再做整体过滤,没选课的学生直接没了。这个细节是第 66 题的核心考点,你刷到的时候一定要亲手跑一遍对比结果。
3.5 子查询与 EXISTS:谁嵌套谁,需要一点反直觉
子查询这部分题,如果你前面的 JOIN 学扎实了,其实会感觉挺轻松的,因为大部分子查询都可以改写成 JOIN。比如“查出选了课程编号为 1 的学生姓名”,既可以写 WHERE id IN (SELECT student_id FROM score WHERE course_id = 1),也可以用前面讲的 JOIN 写出来。两种写法结果一样。
那为什么还要专门出 14 道题练子查询?因为它能帮你搞定一类 JOIN 写起来非常别扭的场景。举一个最经典的例子:“查出没选过任何课程的学生”。这道题翻译成 JOIN 逻辑,你得先做一个多表连接,然后找另一张表里为空的情况,非常绕。但用 NOT EXISTS 特别清爽:
sql复制SELECT s.*
FROM student s
WHERE NOT EXISTS (
SELECT 1 FROM score sc WHERE sc.student_id = s.id
);
我第一次带新人学这个知识点的时候,总有人问:“为什么子查询里写 SELECT 1 而不是 SELECT *?”因为这里的子查询不需要返回具体数据,它只是“判断有没有”的布尔条件,写成 1 或者任意常量,MySQL 在优化器层面根本不会去取字段,执行效率上更好。虽然现在优化器已经很聪明,不管你写 * 还是 1 性能差距微乎其微,但“ EXISTS 子句里只关心有没有记录”这个思路能帮你把逻辑理清。
还有一道考查点很妙:IN 和 EXISTS 的转换。比如,student 表里如果有 NULL 值的主键,用 WHERE s.id NOT IN (SELECT student_id FROM score) 会导致结果集为空,因为 NULL 跟任何值做 NOT IN 比较都会返回 UNKNOWN。而 NOT EXISTS 子查询对 NULL 免疫。这种防不胜防的细节,只有实际遇到过一次,才记得住。
3.6 日期、字符串与窗口函数:从会用到用对
最后两类题在日常工作里特别常用,尤其是 89 题之后涉及日期和字符串处理的内容。讲个真实例子:你想统计上个月的订单量,但数据库里的时间字段是 2024-03-15 08:32:11,你需要先用 DATE_FORMAT(order_time, '%Y-%m') 把月份提取出来,再去跟某个月份值比较。MySQL 的 DATE_FORMAT 里 %Y 是四位的年,%y 是两位的年,%m 是两位的月,%c 是不带前导零的月。这几个格式化串我教过的学生里十个有六个会记混,然后查出来的数据怎么对都对不上,怀疑人生。
字符串处理也一样,第 91 题“把学生姓名按班级分组后用逗号连接成一行”,考察的是 GROUP_CONCAT。这个函数在生成报表时被称为“行转列”的神器。比如你要把某班级的学生名单变成一个字符串,直接 GROUP_CONCAT(name) 就能完成,还能指定分隔符 GROUP_CONCAT(name SEPARATOR '、'),甚至能排序 GROUP_CONCAT(name ORDER BY id SEPARATOR '、')。别看它只是函数,很多做报表的同事全靠它省下了大量时间。
最后那 5 道窗口函数题,是整套题的“加餐”。MySQL 8.0 以上版本才支持窗口函数,如果你还在用 5.7,可以先跳过这部分。但我的建议是不要跳,因为窗口函数在分组 TOP-N 场景太好用了。比如“查出每个班年龄最大的学生信息”,如果用传统写法你得先按班级分组求最大年龄,再回表关联,步骤繁琐而且容易出错。但窗口函数写出来就非常直观:
sql复制SELECT name, class_name, age
FROM (
SELECT name, class_name, age,
ROW_NUMBER() OVER (PARTITION BY class_name ORDER BY age DESC) AS rn
FROM student
) t
WHERE rn = 1;
内层先用 ROW_NUMBER() 对每个班级内的学生按年龄倒序编号,年龄最大的编号是 1,外层筛出编号为 1 的记录就完成了。这个思路一旦掌握,你在业务里遇到任何“分组取前 N 条”的需求,都能一击即中。
4. 刷题过程中最常踩的坑:我的排查思路实录
4.1 GROUP BY 报错:ONLY_FULL_GROUP_BY 模式的恩怨
很多人刚练分组语句的时候,会碰到一条看起来毫无道理的报错:Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column。什么意思?翻译成人话就是:你 SELECT 了一个既不在 GROUP BY 里、也没有被聚合函数包起来的字段,数据库不知道该怎么处理这个字段。
比如,你想按班级分组,然后顺便把学生姓名显示出来:
sql复制SELECT class_name, name, COUNT(*)
FROM student
GROUP BY class_name;
在 MySQL 5.7 以前的版本里,这条语句能跑通,但查出来的 name 是随机的,因为每个班级里有多名学生,数据库只取了一个不确定的。到了 MySQL 5.7 及以后,默认开启了 ONLY_FULL_GROUP_BY 模式,直接报错,反而是在保护你。解决的办法是,要么把 name 也加进 GROUP BY,要么就用聚合函数处理它,例如 GROUP_CONCAT(name)。这个报错我见过无数次,它其实是在提醒你:你想怎么处理“每个分组里多出来的那个字段”?把这个问题想清楚,比绕过报错重要得多。MySQL 5.7 及以上版本的默认行为都是安全的,遇到这类报错别慌,也别一上来就改配置文件关掉模式,先按正确语义调整 SQL。
4.2 NULL 参与的运算与 COUNT 的语义差异
这里有一个我几乎每次带新人都会强调的经典坑:COUNT(*) 和 COUNT(字段) 根本不一样。第 51 题“统计每个班学生的平均年龄”,如果你写成:
sql复制SELECT class_name, AVG(age) FROM student GROUP BY class_name;
问题就来了:如果某个学生的 age 字段是 NULL,AVG 会自动忽略它,求出来的是非空年龄的平均值。这个行为通常是合理的,但如果你天真地以为空值参与计算会被当成 0,你就错了。NULL 和 0 是完全不同的两种状态,0 是一个假想的“他确实 0 岁”,而 NULL 是“没人知道他的年龄”,数据库不做假设。
同样的,统计“每个班有多少人报名了某个活动”,用 COUNT(activity_id) 就会漏掉那些 activity_id 为 NULL 的学生,而用 COUNT(*) 则会全部统计进去。到底用哪一个,取决于业务需求是要“满足某条件的人数”还是“总行数”。不是口诀上那句“COUNT(*) 比 COUNT(1) 快”这么简单,语义才是首要问题。
4.3 数据方言的坑:MySQL 与标准 SQL 的差别
这个题目虽然标题是 MySQL,但很多人练到后面会拿同样的 SQL 去跑 SQL Server 或者 PostgreSQL,结果各种语法报错。MySQL 的 LIMIT 在 SQL Server 里就是 TOP 或者 OFFSET FETCH,SQL Server 里的字符串拼接用 +,MySQL 里用 CONCAT。日期函数更不用说了,各家的函数名和参数格式差异巨大。如果以后要转向其他数据库,这种差异带来的切换成本比想象中高。所以刷这套题的时候,我的建议还是老老实实用 MySQL 8.0,这套题的所有答案都经过 MySQL 8.0 验证,跑起来零阻碍。这不是说 MySQL 是“标准”,而是练基础语法时,它最简单直接,资料也多,好排查。等你把 SQL 的逻辑思维练到一定程度,再去迁到其他数据库,只是记忆层的事,理解层不会归零。
4.4 子查询优化与 EXISTS 的性能误读
有个根深蒂固的说法是“EXISTS 比 IN 快,所以要尽量写成 EXISTS”。这句话在 20 年前可能正确,但现在优化器早就进化了,MySQL 8.0 的优化器会自动把符合条件的 IN 和 EXISTS 转换成半连接(semi-join)来优化,两者在很多场景下性能几乎无差别。真正能决定性能差异的,是你的条件字段有没有索引。我在出第 80 到 85 题时,没指望你写出性能最优的 SQL,因为练习数据量太小,性能根本看不出来,关键是你得写出结果正确的 SQL。等你到了真实环境里面对百万级数据,再谈“慢 SQL 优化”也不迟。到那时候多用 EXPLAIN 看执行计划,比背诵任何优化口诀都管用。
4.5 自连接到底在连谁:理解表副本
第 72 题左右会出现一道自连接的题,比如“查出每一门课程中成绩比平均分高的学生”。这里就需要用到自连接:同一张表连接两次。很多新手在这里彻底晕掉,为什么同一张表能连两次?在 SQL 的逻辑层面,一张表参与多少次查询,就可以生成多少个独立的副本,每个副本有自己独立的别名。你完全可以把它理解为两张结构一样但内容各自独立的表。实际操作中我用过最笨也最有效的教学方法,就是让学生把同一张表手动复制一遍,起名为 score t1 和 score t2,然后再去想连接条件。一旦你能在脑海中把一张表拆成两个“分身”,自连接就瞬间变简单了。
4.6 结果对不上答案:从执行顺序逆推排查
刷题最沮丧的场景就是:自己写的 SQL 能查到结果,但是跟标准答案不一致,又说不清差在哪。我的经验是,永远不要先怀疑标准答案错了,先按下面这套排查顺序来:
- 先看行数差异:多了几行?大概率是多表连接时产生了笛卡尔积,或者连接条件写漏了。
- 再看数值差异:数字偏大或偏小?看看是否该用
DISTINCT去重,或者 COUNT 的字段选对没有。 - 再看边界值:正好卡在边界上对不上,大概率是
BETWEEN边界、>=还是>、<=还是<的问题。 - 再看 NULL:如果某些行在其他工具里显示为空白,但你以为它们是 0 或者空字符串,那么条件判断里就会漏数据。
- 最后再看排序:如果只是顺序不同,检查是否有
ORDER BY;如果涉及分页,检查LIMIT偏移量是从 0 开始还是从 1 开始。
这套排查顺序看起来基础,但真的能解决 80% 的“对不上答案”。它逼着你把 SQL 的执行过程在脑子里跑一遍,长期这样练,你对 SQL 的掌控力会远超那些只会在在线平台上“对答案”的人。
5. 关于练习顺序、时间投入和后续提升的建议
5.1 我的推荐刷题路线:先手写后机跑
如果你把这 100 道题直接顺序刷完,会觉得前 20 道特别枯燥,因为全都是建表和插入。我建议你第一遍可以先快速过,把建表语句直接复制到本地跑通,不用每题都手敲;但从第 21 题开始,建议每题都先自己手写 SQL,写不出来再去看答案。手写的过程其实是在强迫你回忆语法,你在纸上写 SELECT 的时候,脑海里就需要回顾它的执行顺序和字段要求。一旦依赖图形化客户端里的智能提示,你很容易形成一种“会点不会写”的错觉,这是我在带新人时总结出的最大陷阱。
时间安排上,我给一个每周计划做参考:前三天集中突破 01-35 题,重点吃透 DDL 和单表查询;第四五天主攻 36-74 题,把聚合分组的思路和连接查询搞明白;第六七八天完成剩余部分并复习错题。如果遇到某一题卡了超过 20 分钟还毫无头绪,就果断看答案,在答案上标注自己的理解盲点,隔天再凭记忆重写一遍。我自己的经验是,第二天重写会暴露大量“以为会了”的知识点,这种重复比追求“一题一题不看答案硬憋”效率高得多。
5.2 学完这 100 道题之后,下一步怎么延伸
当你把这 100 道题全部跑完,并且能保证每个 SQL 不用查资料就写得出来,你会发现自己的 SQL 水平已经超过了很多号称“会数据库”的同事。但要往更专业的方向走,我建议你紧接着做两件事:
第一,去了解一下 MySQL 的执行计划,给自己写过的几条复杂 SQL 加 EXPLAIN,看看它到底是怎么查的,有没有走索引。练习数据量小,可能看不出区别,但这是通向“慢 SQL 优化”的第一步。
第二,把练习场景从“校园管理”换成你自己的真实业务。比如你在处理订单表,你可以尝试自己提出几个业务问题,然后用 SQL 去解答:最近三个月每月销售额是多少?复购用户占比多少?每个品类的销量排行如何?这些真实场景中的问题,才是检验你 SQL 功底的试金石。
说到底,100 道题只是路标,路还得靠你自己走。我希望这份练习方案,能真正帮你跨过“学了就忘、写了就错”的那道坎。
最后分享一个我自己带人时常用的小技巧:刷到第 80 题以后,如果感觉自己某个模块掌握得不够牢,不要不好意思往回翻,直接用旧题自己改编新题。比如你把“查每个班年龄最大的人”改成“查每个班年龄最小的三位同学”,技术上就变成了窗口函数里用 ORDER BY age ASC 加上 LIMIT 窗口的问题,这种二次加工比连刷十道新题更能检验你的理解深度。等你刷到闭着眼能改编题型,那这套题就算真正吃透了。
