SQL这玩意儿,看十遍书不如自己敲二十条SQL。我这些年带过不少刚入门的新人,也帮同事冲刺过数据库岗位的面试,最后沉淀下来一套MySQL基础练习题,前前后后整理成100道。这套题不谈花哨技巧,就是围绕建表、查询、分组、连接、子查询这些基本功反复练。为什么要练?因为绝大多数人写不对SQL,不是缺乏智商,是缺“手感”——比如知道WHERE和HAVING都能过滤,却不清楚各自在什么时候生效;知道JOIN能把表合起来,遇到左连接为什么右表补了一堆NULL还是栽跟头。这套题覆盖的就是这些最容易被忽略的细节。
如果你刚学完MySQL语法,准备找工作或者做课程设计,这套题可以直接当热身场;如果离面试还有一周,我会建议你直接刷后面的综合模块,用题目把“快忘掉的知识点”重新拽回来。下面我会把题目设计思路、环境准备、核心考点、典型题解和避坑经验全部摊开讲,代码可以直接复制到本地跑。
1. 这套100道练习题的定位与设计思路
1.1 为什么是100道,不是20道也不是500道
一上来先聊一个大家肯定关心的问题:题量为什么偏偏是100道?
20道题肯定不够。SQL知识点看着不多,但每个知识点都有大量变体。一个普通的去重查询,直接DISTINCT是一种,GROUP BY是一种,COUNT(DISTINCT)又是一种;同一个业务需求,用关联子查询能写,用窗口函数也能写。如果没有足够数量的题目去反复刺激,你很难真正形成“一看到表结构,脑子里就浮现出几种可选写法”的条件反射。
500道又太多了。说实话,市面上动辄几百道、上千道的题库,很大一部分是重复题、偏题、炫技题。对于基础阶段的人来说,刷到最后就是机械劳动,边际收益非常低,还容易把信心磨没了。
100道是一个比较合适的数字。我按模块分摊,每个模块10道题,这样每一组都是一个独立的知识单元,练完一组休息一下,心理负担也小。题目难度分布大概遵循7:2:1——七成是基础语法和常见业务写法,两成是有点弯弯绕绕的组合场景,剩下一成是面试里容易出现的进阶写法。这样既能让你建立信心,又不会让整套题练完还在原地踏步。
1.2 十个模块的内容分布
这一百道题不是随手凑的,我按照SQL能力成长路径分成十个模块,每个模块解决一个具体能力点:
| 模块 | 题目范围 | 训练重点 |
|---|---|---|
| 1. 建库建表与约束 | 第1-10题 | 表结构设计、主键/外键/唯一约束、默认值、字符集 |
| 2. 增删改与基础维护 | 第11-20题 | INSERT、UPDATE、DELETE、事务回滚、TRUNCATE |
| 3. SELECT条件查询 | 第21-30题 | WHERE、IN、BETWEEN、LIKE、NULL判断 |
| 4. 排序与表达式 | 第31-40题 | ORDER BY、算术表达式、字符拼接、LIMIT分页 |
| 5. 聚合与分组 | 第41-50题 | COUNT/SUM/AVG/MAX/MIN、GROUP BY、HAVING |
| 6. 多表连接 | 第51-60题 | INNER JOIN、LEFT JOIN、RIGHT JOIN、自连接 |
| 7. 子查询 | 第61-70题 | IN/EXISTS子查询、标量子查询、FROM子查询 |
| 8. 日期与字符串函数 | 第71-80题 | 常用日期函数、文本处理函数、正则匹配 |
| 9. 视图/索引/事务/进阶 | 第81-90题 | 视图、索引、事务、CASE WHEN、行转列、窗口函数 |
| 10. 综合练习 | 第91-100题 | 经典面试场景:Top N、连续记录、选课统计等 |
这样分完之后,你会发现每条SQL练习背后其实都在解决一类查询场景。比如第15题考UPDATE和DELETE,实际是为了让大家区分改数据和删数据的边界;第60题考自连接,是为了处理“同一张表内部存在对应关系”的经典场景。
1.3 推荐刷题节奏与自检标准
我建议的刷题节奏是:不要一天刷完100道,那样下午脑子基本是糊的。比较靠谱的方式是分两个周末,每次刷两个模块。比如第一天集中练第1到第20题,把建表、约束、增删改全部跑顺;第二天练第21到第40题,重点打磨查询和排序。每个模块结束之后,不要急着往下走,先把前一个模块的错题复盘一遍。
复盘的方法特别简单:把题目答案关掉,不看任何提示,重新在白纸上写出这条SQL,然后拿到MySQL里执行。如果一次通过并且结果和预期一致,才说明这个知识点真正变成你自己的了。很多朋友喜欢把答案抄一遍就完事,我实测下来效果最差,下次遇到稍微改个条件的题目,还是写不出来。
每道题给自己设定一个心理时间:基础题最多3分钟,进阶题最多10分钟,综合题最多15分钟。超过这个时间还写不出来,就直接看答案,看懂之后关闭答案再写一遍,直到能默写为止。这比死磕一小时要高效得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 刷题前一天先把环境准备到“零坑”状态
2.1 选择MySQL版本和连接工具
这套题全部基于MySQL,你要去练习,第一步肯定要装一个能跑的MySQL实例。版本我建议直接用8.0及以上,不要再用5.7了。原因是8.0默认字符集就是utf8mb4,对中文更友好,还支持窗口函数、公用表表达式(CTE)这些比较现代的语法。后文的“每科前三名”等进阶题,用窗口函数写非常简洁,如果还在5.7上跑,可能会报语法错误。
安装方式大家按自己的操作系统来。Windows用户直接去MySQL官网下载MySQL Installer,一路默认即可;macOS用户可以把服务用Homebrew装起来,或者下载官方dmg安装包;Linux用户如果手里是云服务器,用包管理器安装后启动mysqld就行。也可以用Docker快速拉一个MySQL 8.0容器,但我个人会更推荐本机直接装,因为刷题时命令行连接、Navicat连接都更方便排查问题。需要多说一句:网上搜安装教程时,很容易搜到SQL Server的教程,SQL Server是另一个数据库产品,语法细节跟MySQL有各种差异,千万别混着装。题库里所有语法都基于MySQL,练习时不要拿SQL Server的写法硬套。
连接工具有三种梯队:第一梯队是MySQL官方自带的命令行,基础刷题完全够用,输入SQL后按分号回车就能看结果;第二梯队是像Navicat这样的图形客户端,可视化建表、看数据更方便,适合后面练视图、看执行计划;第三梯队是VS Code或者DataGrip里的插件,适合长期写SQL的人。不要在这个选择上花太多时间,选一个你用着顺手的,把精力留给题目本身。
2.2 建一个经典的学生-课程-成绩库
为了让大家练习的场景统一,我准备了下面这套基础数据。注意课程表中有一门“软件工程”没有学生选,学生表里也有一个“周涛”没有成绩,这是故意留出来的,后期做LEFT JOIN和子查询时能派上大用场,千万别觉得多余就删掉了。
把下面的SQL保存成一个文件(比如init_sql100.sql),在MySQL里执行即可:
sql复制DROP DATABASE IF EXISTS sql100;
CREATE DATABASE sql100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE sql100;
-- 学生表
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '学生编号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
gender CHAR(1) COMMENT '性别',
birth DATE COMMENT '出生日期',
class_name VARCHAR(50) COMMENT '班级'
);
-- 课程表
CREATE TABLE course (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '课程编号',
course_name VARCHAR(100) NOT NULL COMMENT '课程名称',
credit DECIMAL(3,1) COMMENT '学分',
teacher VARCHAR(50) COMMENT '授课老师'
);
-- 成绩表
CREATE TABLE score (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '成绩记录编号',
student_id INT NOT NULL COMMENT '学生编号',
course_id INT NOT NULL COMMENT '课程编号',
score DECIMAL(5,1) COMMENT '成绩',
exam_date DATE COMMENT '考试日期',
CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(id),
CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course(id)
);
-- 插入学生数据
INSERT INTO student (id, name, gender, birth, class_name) VALUES
(1, '李明', '男', '2003-02-11', '2022级计算机1班'),
(2, '王雪', '女', '2002-05-03', '2022级计算机1班'),
(3, '张伟', '男', '2003-07-22', '2022级计算机2班'),
(4, '刘洋', '男', '2004-01-15', '2022级计算机2班'),
(5, '陈静', '女', '2003-12-30', '2022级计算机1班'),
(6, '赵磊', '男', '2002-09-09', '2023级软件1班'),
(7, '孙悦', '女', '2004-04-18', '2023级软件1班'),
(8, '周涛', '男', NULL, '2023级软件2班');
-- 插入课程数据
INSERT INTO course (id, course_name, credit, teacher) VALUES
(1, '数据库基础', 3.0, '王老师'),
(2, '数据结构', 4.0, '李老师'),
(3, 'Java程序设计', 4.0, '张老师'),
(4, 'Python数据分析', 3.0, '刘老师'),
(5, '计算机网络', 3.5, '陈老师'),
(6, '软件工程', 2.0, '赵老师');
-- 插入成绩数据
INSERT INTO score (student_id, course_id, score, exam_date) VALUES
(1, 1, 88, '2024-06-20'),
(1, 2, 79, '2024-12-18'),
(2, 1, 92, '2024-01-15'),
(2, 3, 85, '2024-03-10'),
(3, 1, 66, '2024-06-20'),
(3, 3, 58, '2024-06-21'),
(4, 4, 95, '2024-03-12'),
(5, 1, 79, '2024-06-20'),
(5, 2, 90, '2024-12-18'),
(6, 3, 83, '2024-06-21'),
(7, 4, 88, '2024-03-12'),
(7, 5, 76, '2024-06-26');
为什么要用学生、课程、成绩这三张表?因为这是关系型数据库最经典的教学模型。student和course是多对多关系,score作为中间表把它们关联起来,这正好覆盖了实际业务里最常见的“事实表 + 维度表”结构。你把这三张表的练熟了,以后面对订单表、商品表、用户表,会发现本质上是同一套逻辑。
2.3 导入后先跑几条SQL验证环境
数据导入完成之后,不要直接开始刷题,先跑几条SQL确认环境是可用的。这里我习惯用三条语句快速验证:
sql复制USE sql100;
SELECT COUNT(*) AS student_cnt FROM student;
SELECT COUNT(*) AS course_cnt FROM course;
SELECT COUNT(*) AS score_cnt FROM score;
如果分别返回8、6、12,说明数据导入成功。接着再跑一条最简单的JOIN,验证表关系能正常连起来:
sql复制SELECT s.name, c.course_name, sc.score
FROM student s
JOIN score sc ON s.id = sc.student_id
JOIN course c ON c.id = sc.course_id
ORDER BY s.id, c.id
LIMIT 5;
看到几条中文记录正常返回,就说明字符集没问题,环境基本可以放心使用了。
2.4 常见环境报错处理
刷题过程中,最常见的环境类报错就是ERROR 2002。完整提示一般是:
text复制ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)
这个报错最直接的翻译是“MySQL服务没起来,或者客户端访问的socket路径不对”。排查时有几个顺序:先用系统服务管理命令看看mysqld进程是否在运行;再检查连接时是不是指定了错误的端口、socket文件路径或host。很多时候,你安装了MySQL但没启动服务,就会看到这个提示。它跟SQL语法无关,环境恢复之后就不会再出现了。
3. 100道题背后真正想练的六个底层能力
3.1 SELECT查询与WHERE条件:先形成“写SQL就是筛数据”的意识
很多人写SQL的最大问题,不是记不住语法,而是不知道该怎么把一个自然语言问题翻译成严格的过滤条件。比如“想找出计算机1班的男生”,这句话翻译成SQL就是:先定位到student表,再让WHERE同时满足班级等于计算机1班、性别等于男这两个条件。
在基础练习里,我会专门安排几道关于NULL的题。WHERE条件里最容易翻车的就是NULL比较。比如“查询出生日期没有填写的学生”,新手经常会写成:
sql复制SELECT * FROM student WHERE birth = NULL;
这样查不出来任何结果。因为NULL不是一个具体的值,它表示“未知”,不能用等号去判断,必须写成:
sql复制SELECT * FROM student WHERE birth IS NULL;
反过来,想查“出生日期已经填写的学生”,要写IS NOT NULL。这个点非常基础,但几乎每期带新人都会有人踩,所以一定要在做题时养成肌肉记忆。
3.2 去重、区间匹配与模糊查询:DISTINCT/BETWEEN/LIKE易错点
去重查询是搜索引擎里的高频热词,也是SQL练习题里的常客。最直接的去重写法是DISTINCT,但新手很容易误以为DISTINCT只是去掉某一列的重复值。注意,SELECT DISTINCT后面跟多个字段时,去重依据是“多个字段的组合”,不是单独某一列。举个例子:
sql复制SELECT DISTINCT class_name FROM student;
这个语句能把班级去重,得到4个不同的班级。但如果写成:
sql复制SELECT DISTINCT class_name, gender FROM student;
那结果会变成“每个班级+每种性别的组合”,原本同样一个班级,因为男女生性别不同,可能输出两行。这个区别非常重要,很多练习里的去重题,其实是在考这个理解。
BETWEEN AND也是一个容易踩坑的点。比如题目要求“查询成绩在80到90之间的记录”,对应SQL是:
sql复制SELECT * FROM score WHERE score BETWEEN 80 AND 90;
这里BETWEEN是包含两端的,也就是会包含等于80和等于90的数据。如果业务想表达的是“80及以上、90以下”,就必须换成大于等于和小于的组合,不能沿用BETWEEN。这种边界问题,在真实统计报表里经常会导致数据对不上,非常值得留意。
模糊查询LIKE的使用也有自己的逻辑。%代表任意多个字符,_代表单个字符。比如“学生姓名中包含‘李’字”,SQL是:
sql复制SELECT * FROM student WHERE name LIKE '%李%';
