SQL八套练习题:从单表查询到多表连接,动手练熟
简介面向 MySQL 初学者与开发者的 SQL 单表及多表查询题集含四套单表、四套多表练习题覆盖从基础语法到多表关联的常见场景可帮助读者快速定位查询薄弱环节并强化训练。压缩包为 RAR 格式共 19 个文件其中文本文件保存各套题目与练习说明SQL 脚本用于建库、插入数据并执行查询另附带答案压缩包整体仅 22KB下载和本地练习都很便捷。这套题集已有 911 人学习/下载常被用于查询入门、期末复习与面试前集中刷题题型设计贴近实际工作需求。内容按单表、多表模块化整理单表题重点训练条件过滤、排序、分组与聚合函数多表题则覆盖内连接、左连接、右连接与子查询等核心操作。结合每套答案与对应脚本一起对照练习既能理清查询执行的先后顺序也能较快发现常见易错点适合想要系统提升数据库查询能力的学习者反复演练。 我见过太多人SQL是“看会”的——教程刷了几十篇收藏夹里塞满sql语句大全可真到面试现场或者接手一个取数需求连单表的分组统计都写不顺。这不是基础差是练得少。SQL这门技能很特殊语法规则几天就能背完但它真正难的地方在于把脑子里的模糊诉求翻译成准确、高效的单表或多表查询语句。所以这次我整理了单表四套、多表四套共八套SQL练习题从最基础的条件查询一路做到分组聚合、自连接、关联子查询和排名场景专门用来把手练熟。不论你是刚学完语法、缺少实战的初学者还是准备跳槽面试前想快速找回手感的老手这套题都值得从头到尾完整写一遍。1. 这套题为什么按“单表四套多表四套”来设计1.1 单表先于多表先把一张表玩明白很多人一开始就冲JOIN和子查询结果连一张表里的WHERE和GROUP BY都没理顺。SQL的难度是递进的单表查询是地基多表连接是在地基上盖楼。你连“每个部门平均工资”这种单表聚合都写不利索换成“每个部门里工资最高的员工”这种多表场景只会更懵。这八套题前四套只碰一张表把所有过滤、排序、分组、函数处理练扎实后四套才引入第二张表专注于各种连接类型和关联写法。顺序上不要跳单表不过关多表一定卡壳。1.2 四套题不是重复是四个能力层次同样都是单表第一套和第二套练的是“把数据找出来、排好序”第三套开始练“把数据算出来”第四套则是“把数据加工出来”。这四个层次对应了日常取数工作中最常见的四类需求查明细、看排名、做统计、做转换。多表四套也一样第一套是标准的内连接取数第二套是外连接处理“有或无”的问题第三套是自连接和子查询这种稍微绕一点的逻辑第四套是甩开单表思维、直接模拟真实经营分析场景。每套解决一类问题写完之后你再看实际业务需求心里会先自动做归类。1.3 每道题控制在十分钟内写完这套题的定位是“练习”而不是“考试难点收集”所以我没有故意出偏题怪题。几乎所有题目都是实际工作或面试里反复出现的写法。我建议你拿到题目后不要先看答案给自己十分钟脑子里过一遍逻辑再动手敲。能独立写出来的题说明这个知识点真的是你的了写不出来的对照答案之后一定要再默写一遍。这套题能不能发挥作用不取决于题目本身而取决于你“重复写”的次数。2. 单表四套把WHERE、排序、分组、函数这几个基本功逐层夯实2.1 第一套单表条件查询WHERE是SQL一切能力的起点第一套题表面上简单但它的坑也最多。这四道题覆盖了WHERE子句里最常见的比较、区间、模糊匹配和NULL判断。题目查询工资大于8000的员工姓名和工资。SELECT emp_name, salary FROM employee WHERE salary 8000;题目查询2018年1月1日之后入职、并且姓名以“张”开头的员工。SELECT emp_name, hire_date FROM employee WHERE hire_date 2018-01-01 AND emp_name LIKE 张%;题目查询部门编号为1或2的员工。SELECT emp_name, dept_id FROM employee WHERE dept_id IN (1, 2);题目查询没有分配部门部门编号为空的员工。SELECT emp_name, dept_id FROM employee WHERE dept_id IS NULL;为什么强调这套题因为工作中大量查询的起点就是“把满足某几个条件的记录先捞出来”。这里有三个非常容易翻车的点第一判断NULL不能用等号必须用IS NULL或IS NOT NULL用 NULL查出来永远是空结果第二多个过滤条件混用时AND和OR的优先级容易搞混拿不准就直接加括号第三字符串和时间比较要用引号包起来而且日期格式尽量写成YYYY-MM-DD别写成人人都看不懂的2018/1/1。这套题只要能一边不看答案、一边顺利写出来就说明你已经跨过了“能看懂SQL但写不出来”那道坎。2.2 第二套单表排序与分页ORDER BY没有你想的那么简单排序只是加一个ORDER BY吗是但排序和分页组合在一起才是面试里反复出现的考点。题目查询工资最高的5名员工。SELECT emp_name, salary FROM employee ORDER BY salary DESC LIMIT 5;如果是在SQL Server里写法要换成SELECT TOP 5 emp_name, salary FROM employee ORDER BY salary DESC;题目按部门编号升序、工资降序排列所有员工。SELECT emp_name, dept_id, salary FROM employee ORDER BY dept_id ASC, salary DESC;题目分页查询第2页数据每页10条。SELECT emp_name, salary FROM employee ORDER BY emp_id LIMIT 10 OFFSET 10;写这道题时我倒想多说一句LIMIT 10 OFFSET 10的意思是跳过前10条、取接下来的10条也就是第二页如果你用了LIMIT 10, 10这种逗号写法含义一样但别跟LIMIT 10 OFFSET 10记混。很多人在分页上翻车不是不知道语法而是搞不清“每页10条取第2页”到底该跳过几条。第1页是OFFSET 0第2页是OFFSET 10每页条数越大OFFSET就是(页号-1)乘以每页条数。排序和分页是报表类需求里最高频的组合多写几次就自然记住了。2.3 第三套单表聚合与分组GROUP BY是单表查询的分水岭从这套题开始你已经不只是“查数据”而是“算数据”了。聚合函数配合GROUP BY能不能把分组逻辑想清楚基本决定了你SQL水平的上限。题目统计每个部门的员工人数。SELECT dept_id, COUNT(*) AS emp_count FROM employee GROUP BY dept_id;题目统计每个部门的平均工资并且只显示平均工资大于8000的部门。SELECT dept_id, AVG(salary) AS avg_salary FROM employee GROUP BY dept_id HAVING AVG(salary) 8000;题目查询公司里最高工资和最低工资的差额。SELECT MAX(salary) - MIN(salary) AS salary_diff FROM employee;很多人在这个阶段最常犯的错误是把WHERE和HAVING搞混。WHERE是在分组之前过滤原始记录HAVING是在分组之后过滤聚合结果。比如“统计每个部门里工资大于8000的员工人数”这个“大于8000”是在分组之前先按条件筛掉一部分人应该写在WHERE里而“只显示平均工资大于8000的部门”这个条件依赖聚合结果必须写在HAVING里。另一个容易忽视的点是COUNT(*)统计的是行数而COUNT(salary)统计的是salary列非空的个数二者在有NULL值的时候结果可能完全不一样。这套题是整套练习里最重要的关卡建议每题都手动敲两遍以上。2.4 第四套单表DISTINCT、CASE WHEN与日期函数这一套开始涉及“加工数据”——不是单纯地把数据库里的值拿出来而是把它们变成你想要的样子。题目查询公司一共有多少个不同的部门编号。SELECT COUNT(DISTINCT dept_id) AS dept_count FROM employee;题目给员工按工资划分等级8000以上为“高”5000到8000为“中”5000以下为“低”。SELECT emp_name, salary, CASE WHEN salary 8000 THEN 高 WHEN salary 5000 THEN 中 ELSE 低 END AS salary_level FROM employee;题目查询2018年以后入职的员工姓名和入职年份。SELECT emp_name, YEAR(hire_date) AS hire_year FROM employee WHERE hire_date 2018-01-01;题目统计男女员工各多少人。SELECT gender, COUNT(*) FROM employee GROUP BY gender;这套题的核心就是让你意识到SQL不只可以做“过滤”和“计算”还可以做“变换”。CASE WHEN在真实报表里使用频率非常高比如把存量的状态值转成可读的中文标签、把连续数值切成几个区间段来做分布统计。日期函数在不同数据库里写法差异比较大MySQL的YEAR(hire_date)在SQL Server里同样可用但如果是Oracle就要用EXTRACT(YEAR FROM hire_date)。我建议你练习时把自己常用的那款数据库的日期函数查一遍记在笔记里因为日期处理永远是SQL实践里绕不开的大头。3. 多表四套连接、自关联、子查询与排名场景的真实业务组合3.1 第一套多表INNER JOIN连接的基础逻辑多表查询是从这里真正开始的。前面四套题不管你写得怎么样只要到这个地方还在用“先把两张表拼成一张大表”的思维做题那后面几套会越来越吃力。题目查询员工的姓名和所属部门名称。SELECT e.emp_name, d.dept_name FROM employee e INNER JOIN department d ON e.dept_id d.dept_id;题目查询工资大于8000的员工姓名、部门名称和工资。SELECT e.emp_name, d.dept_name, e.salary FROM employee e INNER JOIN department d ON e.dept_id d.dept_id WHERE e.salary 8000;题目查询每个部门的部门名称和它的经理姓名。这里假设department表里的manager_id对应employee表的emp_id。SELECT d.dept_name, e.emp_name AS manager_name FROM department d INNER JOIN employee e ON d.manager_id e.emp_id;为什么强调要使用表别名因为多表查询里两张表可能都有emp_id、dept_id这种同名字段不写别名数据库根本分不清你在说哪一个最后只能报“字段不明确”的错误。第一套题的关键在于理解INNER JOIN的本质它只返回两张表里能匹配上的行匹配不上的记录会被直接丢弃。这意味着如果员工没有分配部门那么他在这个查询结果里就“消失”了。能不能接受这种“消失”取决于业务需要什么这也是下一套题要解决的问题。3.2 第二套多表LEFT JOIN与NULL的坑上一套里员工没部门会消失这一套题就是为了处理“即使没匹配上我也要看到它”的场景。题目列出所有部门以及每个部门的员工人数没有员工的部门也要显示人数显示为0。SELECT d.dept_name, COUNT(e.emp_id) AS emp_count FROM department d LEFT JOIN employee e ON d.dept_id e.dept_id GROUP BY d.dept_id, d.dept_name;题目查找没有任何员工的部门。SELECT d.dept_name FROM department d LEFT JOIN employee e ON d.dept_id e.dept_id WHERE e.emp_id IS NULL;这两道题是整套练习里最容易写错的地方而且错得非常隐蔽。拿第一题来说很多人会写COUNT(*)而不是COUNT(e.emp_id)。如果某个部门没有员工LEFT JOIN的结果里会出现一行“部门信息 全是NULL的员工字段”COUNT(*)会把这一行也算进去结果人数显示成1。这是“看起来对、实际错”的典型代表面试官一眼就能识别出来。第二题用WHERE e.emp_id IS NULL来筛出“右表没匹配上”的部门思路其实就是“反连接”。这套题练完你对LEFT JOIN的理解会上升一大截。3.3 第三套多表自连接与关联子查询自连接和关联子查询是SQL里公认的“分水岭”很多人学到这儿会卡很久。但其实把它们拆开看就是把同一张表当成两张表用或者在内层查询里引用外层查询的字段。题目查询员工姓名和直接上级的姓名。假设employee表里的manager_id存的是上级的emp_id。SELECT e.emp_name AS employee_name, m.emp_name AS manager_name FROM employee e LEFT JOIN employee m ON e.manager_id m.emp_id;题目查询工资高于本部门平均工资的员工姓名、部门编号和工资。SELECT e.emp_name, e.dept_id, e.salary FROM employee e WHERE e.salary ( SELECT AVG(salary) FROM employee WHERE dept_id e.dept_id );自连接的写法核心是在FROM里同一张表出现两次分别起不同的别名连接条件就写“员工表的经理ID等于经理表的员工ID”。这里我用的是LEFT JOIN因为最大的老板没有上级如果用INNER JOIN他会被丢掉不符合业务直觉。关联子查询的执行逻辑比较反直觉很多人以为子查询只执行一次其实对于外层表的每一行关联子查询都会用当前行的dept_id去算一次平均工资然后判断当前行是否大于这个平均值。数据量大时这种写法性能不一定好但作为练习题它能帮你把“一行一行地思考SQL”这个思维方式建起来。3.4 第四套多表排名场景与多表聚合走到这一套基本就是在模拟真实的报表分析需求了。前面所有练习都会在这里汇合。题目查询每个部门工资前三名的员工。先用基础写法不依赖窗口函数SELECT e.emp_name, e.dept_id, e.salary FROM employee e WHERE ( SELECT COUNT(*) FROM employee WHERE dept_id e.dept_id AND salary e.salary ) 3 ORDER BY e.dept_id, e.salary DESC;再给出窗口函数版本以MySQL 8.0或SQL Server为例SELECT emp_name, dept_id, salary FROM ( SELECT emp_name, dept_id, salary, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn FROM employee ) t WHERE rn 3;题目查询人数超过2人的部门显示部门名称和人数按人数降序排列。SELECT d.dept_name, COUNT(e.emp_id) AS emp_count FROM department d LEFT JOIN employee e ON d.dept_id e.dept_id GROUP BY d.dept_id, d.dept_name HAVING COUNT(e.emp_id) 2 ORDER BY emp_count DESC;第一道题是面试里高频的“分组TopN”问题。不用窗口函数的写法思路是数一数“同一部门里工资比我高的人有几个”如果少于3个那我就是前三名。这个写法能跑但数据量大时性能一般因为每一行都要执行一次子查询。窗口函数ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC)是标准的分组排序工具PARTITION BY按部门分组ORDER BY salary DESC在组内排序最后在子查询外面过滤rn小于等于3。如果你还不会窗口函数强烈建议把这套玩法和ROWNUMBER、RANK、DENSE_RANK这三个常用排序函数的区别查清楚这已经是现代SQL里躲不开的技能了。4. 建表语句和造数建议先把这几张表跑起来4.1 建表语句纸上谈兵没意思这套题要真练先把环境搭起来。我用的是MySQL风格建表换成SQL Server只需调整等小细节CREATE TABLE department ( dept_id INT PRIMARY KEY, dept_name VARCHAR(50), manager_id INT ); CREATE TABLE employee ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50), dept_id INT, manager_id INT, salary DECIMAL(10, 2), hire_date DATE, gender CHAR(1) );4.2 造数建议表建好之后建议先插入部门数据再插入员工数据。如果你手头没有现成的数据生成工具可以人工造十来个部门、几十个员工注意几个坑设计一些没有员工的部门让LEFT JOIN的题能用上给个别员工的dept_id留成NULL让IS NULL的判断有数据可测manager_id要保证指向的员工确实存在否则自连接那题会查不到结果。我实际测试时大约造了8个部门、30个员工就足够覆盖全部题目了。4.3 不同数据库的语法差异提示这套题里的SQL以MySQL为主但它其实不绑定任何数据库。唯一需要留意的是几个差异分页上MySQL用LIMIT ... OFFSET ...SQL Server用OFFSET ... FETCH NEXT ... ROWS ONLY或TOP字符串拼接MySQL用CONCAT()SQL Server用日期函数方面MySQL有YEAR()SQL Server也有YEAR()但其他日期处理函数差异比较大。练的时候先选一款你实际环境里有的数据库把语法对照着改一遍本身就是很有价值的练习。5. 刷完这八套题之后我总结出的SQL练习心得5.1 做题时最容易踩的六个坑这套题我自己来回验过好几遍也看着不少初学者写过总结出六个最高频的坑第一COUNT(*)和COUNT(列名)混用。LEFT JOIN统计右表匹配数量时想按某列非空计数就别滥用星号否则NULL值那行会多算进去。第二WHERE和HAVING用反。分组前的过滤条件写到HAVING里轻则结果和预期不一样重则直接报语法错误。第三判断NULL用等号。数据库里NULL代表“未知”任何 NULL的运算结果都是“未知”所以查不到任何行必须用IS NULL。第四多表查询漏写连接条件。一旦JOIN后面忘记写ON两张表会做笛卡尔积返回行数瞬间爆炸数据量一大还会把数据库拖垮。第五排序和分组字段不一致。GROUP BY选了dept_idSELECT里却混入emp_id这种非聚合、非分组字段MySQL默认可能不报错但结果毫无意义。第六不看实际结果只背答案。SQL不是默写题同一个需求有无数种写法手写一遍、执行一遍、看到真实输出才叫真的会了。5.2 一套题的检验标准最后说说怎么算“刷完了”。我的标准一直都很简单把答案全部遮住重新打开一个新的SQL窗口从建表开始到所有题都能不看笔记独立写出来并且执行结果和预期完全一致这才叫过关。第一次做不出来的题不丢人丢人的是做完之后从不回头再写一遍。平时练的时候我建议每写完一行SQL就顺手格式化一下保持缩进清晰因为你在练习里养成的书写习惯会原封不动带进面试和实际工作里。这套八套题至少值得你刷两轮——第一轮学思路第二轮拼速度两轮都过完之后你再看那些“sql语句大全”类的收藏会发现大部分内容你已经不需要背了。本文还有配套的精品资源点击获取