资讯详情

资讯详情

SQL语句练习题:单表与多表四套题搞定查询基本功

简介这是一份面向 MySQL 学习者的 SQL 语句练习题集适合新手入门以及开发者巩固查询功底。资源围绕单表查询和多表查询两条主线单表部分覆盖条件过滤、排序、分组与聚合统计多表部分涉及内连接、左连接、右连接、子查询等常用关联写法帮助理解多数据源整合逻辑。压缩包共 19 个文件以 txt 题目说明、sql 脚本和答案压缩包为主整体仅 22KB轻量便捷可直接导入 MySQL 环境演练。目前已有 916 人学习使用。通过四套单表与四套多表练习及配套答案读者能系统梳理 SQL 语法脉络掌握多表关联与嵌套查询的解题思路并在实操中快速定位语句错误、对照答案查漏补缺。题目难度由浅入深从基础查询到综合关联适合日常自学或培训考核是提升 SQL 实战能力的实用资料。1. 一套sql语句练习题单表多表各四套到底能测出什么很多人在真正开始面试前会把“会写SQL”等同于“能跑通几条SELECT”。真到现场写题条件一多、表一关联就卡在连表行数、聚合边界这类基础但致命的地方。这时候“sql语句练习题单表多表各四套”这类结构化练习的价值就出来了。它不追求偏题怪题而是把SQL基本功拆成两个大方向单表四套解决单张表内的筛选、排序、聚合、去重多表四套解决两张及以上表之间的连接、子查询、分组统计。对准备笔试面试的开发者、刚带完SQL基础课需要布置作业的新手、以及想给自己做一次语法体检的日常开发这套题都是一个低成本的自测工具。关键不是题本身有多难而是它能暴露你对执行顺序、NULL值、行数变化这些底层规则是否真的理解而不是靠背语法混过去。2. 先把题拆开单表四套和多表四套各自在考什么2.1 单表四套的公共骨架筛选、聚合、排序与去重单表练习的核心是把一张表的操作练透。市面上常见做法的四套题基本覆盖下面四个维度第一套是条件筛选与字段运算考WHERE、比较运算符、逻辑组合偶尔加一点CASE WHEN第二套是排序、分页与去重考ORDER BY、LIMIT、DISTINCT第三套是聚合统计考GROUP BY、HAVING、COUNT/SUM/AVG/MAX/MIN第四套是日期与字符串处理考DATE_FORMAT、DATE_ADD、SUBSTRING等函数和NULL边界。我一般会用一张订单表来组织这些题。模拟一套最小结构代码如下CREATE TABLE orders ( order_id INT PRIMARY KEY, user_id INT NOT NULL, product_id INT, amount DECIMAL(10,2), status VARCHAR(20), order_date DATE ); INSERT INTO orders VALUES (1, 101, 201, 199.00, paid, 2024-12-01), (2, 102, 202, 59.90, refunded, 2024-12-02), (3, 101, 203, 299.00, paid, 2024-12-05), (4, 103, 201, 199.00, pending, 2024-12-08);上面这段建表和插入语句我建议你在本地练习库里跑一遍。order_id是主键user_id用来区分用户status里的pending和refunded是专门为条件筛选和聚合统计准备的边界值。这样一组数据虽然简单但足够覆盖四套单表题里大部分查询场景。对于字段类型amount用DECIMAL而不是FLOAT是为了避免金额出现浮点误差这也是练习题里一个容易忽略的细节。2.2 多表四套的四个场景连接、子查询、自关联与窗口多表四套更贴近真实业务常见做法的拆分逻辑是第一套只考两表INNER JOIN要求写出连接条件和结果行数第二套考LEFT JOIN与RIGHT JOIN特意制造右表不匹配的NULL第三套考子查询包括IN、EXISTS、标量子查询还有一些场景需要自连接第四套考分组聚合跨表即先连表再GROUP BY或使用窗口函数做排名。我这里的个人经验是前三套解决“能不能查出来”第四套解决“查得巧不巧”。连表题最忌讳的是只看结果对不对不看中间行数变化。比如LEFT JOIN后结果行数比左表多了很多时候不是数据错了而是右表有重复的关联键。这类题的价值就是逼你把连接语义搞清楚。下面用一个模拟的用户和订单模型来搭多表环境CREATE TABLE users ( user_id INT PRIMARY KEY, user_name VARCHAR(50), city VARCHAR(20) ); CREATE TABLE user_orders ( order_id INT PRIMARY KEY, user_id INT, product_name VARCHAR(50), price DECIMAL(10,2) ); INSERT INTO users VALUES (101, A同学, 上海), (102, 某开发者, 北京), (103, 测试用户, 广州); INSERT INTO user_orders VALUES (1, 101, 键盘, 199.00), (2, 101, 鼠标, 59.90), (3, 102, 显示器, 1299.00);注意看user_orders里的user_id没有外键约束这不是疏忽而是故意的。真实业务里经常出现订单表里挂着不存在的用户有外键反而练不出LEFT JOIN的NULL场景。users里103号用户在user_orders中没有订单这个不对称关系就是为了让后续练习里“没买过东西的用户”能被查出来。建表后先自己跑一下两张表各自的COUNT再连表查记下结果行数这是所有多表题的起点。2.3 两组题的衔接为什么先单表后多表单表四套练的是“一张表内的操作能力”多表四套练的是“多张表之间的关联分析能力”。没有前者打底后者很容易翻车。比如连表之后再用GROUP BY如果单表聚合都不熟练很容易把分组的字段选错或者忘记HAVING和WHERE的执行顺序。反过来只练单表不做多表又会对JOIN产生的行数膨胀没有体感一到真实业务就靠猜。所以我的建议是先花几天把单表四套的题全部跑完每道题用至少两种写法实现然后才进入多表四套。多表题里如果卡住了回头去查单表基本功绝大多数情况下问题不是JOIN语法而是WHERE条件或者聚合边界没搞清楚。这套衔接逻辑也提醒一点练习题不是刷得越多越好而是每一题都要能说出为什么这样写。3. 单表四套的练习路径从条件筛选到聚合边界3.1 第一、二套的核心是 WHERE 与 ORDER BY 的执行顺序单表四套最容易被轻视的就是WHERE和ORDER BY的组合。很多刚入门的人以为SQL执行是从SELECT开始的这是最大的误解。SQL的书写顺序是 SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT但逻辑执行顺序完全不同FROM 先定位表WHERE 先过滤行GROUP BY 再分组HAVING 再过滤组ORDER BY 最后排序SELECT 在最后才投影字段。这个差异在单表题里最直接的体现就是你不能在WHERE里使用SELECT里取的别名。-- 正确写法WHERE 里直接用原始字段 SELECT user_id, amount FROM orders WHERE amount 100; -- 错误写法WHERE 里引用 SELECT 别名部分数据库直接报错 SELECT user_id, amount AS amt FROM orders WHERE amt 100;上面这段对比建议把两段都跑一遍。第一段能正常返回第二段在多数数据库里会报“未知列 amt”。这不是语法层面的问题而是执行顺序决定的WHERE 在 SELECT 之前执行此时别名还不存在。你在练习时若遇到报错先别急着改写法想一想是不是自己在WHERE里用了别名。另外ORDER BY 阶段因为表达式比WHERE晚执行所以ORDER BY可以使用别名这也是一个常见的区分点。排序的练习不要只按一列排要多列排序因为多列排序的优先级经常被搞错。-- 先按 status 升序再按 amount 降序 SELECT user_id, status, amount FROM orders WHERE status ! refunded ORDER BY status ASC, amount DESC;这里status ASC和amount DESC的顺序是有讲究的先满足第一个排序键再在第一个键相同的前提下按第二个排序。把ASC和DESC看错位置是这类题丢分的高频原因。LIMIT配合ORDER BY做分页时要注意排序字段必须唯一否则分页结果可能出现重复或跳行这也是后面避坑章节要讲的重点。3.2 第三套聚合统计的多种写法与 HAVING 边界聚合统计是单表四套里含金量最高的一组题。最典型的场景是“按状态统计订单数量总和”很多人第一反应就是GROUP BY status但做完之后有几个细节对不上COUNT(*)和COUNT(列)的结果不一致、HAVING和WHERE的位置写反、分组后 SELECT 里出现了不在分组键里的普通列。这些问题在真实操作里非常常见。-- 按订单状态分组统计每组的订单数和金额总和 SELECT status, COUNT(*) AS order_cnt, SUM(amount) AS total_amount FROM orders GROUP BY status HAVING COUNT(*) 1 ORDER BY total_amount DESC;这段代码里GROUP BY status定义了分组的键后续所有聚合函数都针对每个组进行计算。HAVING是在分组后过滤组级别的条件它和WHERE最大的区别是WHERE过滤行HAVING过滤组。如果你想只统计“非退款”的订单应该把它放在WHERE里因为状态过滤不涉及聚合结果但如果你想过滤“订单数超过5个”的组就必须用HAVING因为COUNT(*)是在分组后才算出来的。这个边界我在带人练习时反复强调真正容易错的不是语法而是逻辑上分不清哪一步先执行。建议练习时把上面SQL里的WHERE和HAVING互换位置跑一次观察报错提示这种报错比记理论更不容易忘。3.3 第四套去重与日期处理的常见误用去重和日期处理是单表题最后一道坎。DISTINCT看着简单实际用起来容易踩两个坑一是DISTINCT作用于多列时是整行去重二是DISTINCT用在聚合函数里面比如COUNT(DISTINCT user_id)跟先DISTINCT再COUNT语义不一样。日期处理则更琐碎不同数据库的函数名不同DATE_FORMAT在另一种数据库里可能叫TO_CHAR这要求你把逻辑和方言分开理解。-- 统计不同用户的订单数排除退款单 SELECT COUNT(DISTINCT user_id) AS active_user_cnt FROM orders WHERE status ! refunded;这里的COUNT(DISTINCT user_id)只对user_id这一列去重后计数结果是一个数字。如果去掉DISTINCT结果就变成所有非退款订单的条数这两个数字含义完全不同。练习时可以把两条SQL并排放对比结果你会发现一个常见误解被数据直接纠正了。日期处理方面比较常见的是“查询最近7天的订单量”这类题写法上有两种一种是在外部写死日期另一种是order_date DATE_SUB(CURDATE(), INTERVAL 7 DAY)。推荐写后者因为它让SQL自己计算日期边界更能体现参数化思维。注意INTERVAL 7 DAY的语法顺序不同数据库有细微差别练习题里一般不用知名数据库的名字去考差异但你自己练习时最好确认一下当前环境支持的写法。4. 多表四套的练习路径连表、子查询与窗口4.1 前两套把 INNER JOIN 和 LEFT JOIN 的差异练透多表四套的前两套核心是连接方式。INNER JOIN 返回两边都匹配的行LEFT JOIN 以左表为基准右表没有匹配就补 NULL。这个区别书面上一句话实际操作中的难点在于你能否预判结果行数。很多人在打印结果前完全不知道会发生什么这很危险因为连表查询在真实业务里会涉及百万级数据行数膨胀会造成严重的性能问题。-- 查询所有用户及其订单没有订单的用户也要显示 SELECT u.user_id, u.user_name, o.order_id, o.product_name FROM users u LEFT JOIN user_orders o ON u.user_id o.user_id ORDER BY u.user_id;这段SQL的结果里用户“测试用户”那一行会有 NULL 的order_id和product_name因为他在user_orders里没有匹配。这里有一个很值得注意的点如果把WHERE u.city 上海放在这条语句的后面结果没问题但如果把WHERE o.product_name 键盘加到这条 LEFT JOIN 后面你可能会发现自己丢失了所有没有订单的用户。原因是 WHERE 在 JOIN 之后执行它把右表为 NULL 的行过滤掉了LEFT JOIN 的语义被破坏。这不是SQL语法错误而是逻辑错误也是多表练习中常见的“LEFT JOIN 白写了”现象。正确做法是把右表条件放在 ON 后面如下SELECT u.user_id, u.user_name, o.product_name FROM users u LEFT JOIN user_orders o ON u.user_id o.user_id AND o.product_name IN (键盘, 鼠标);这段把product_name的过滤条件放在了ON子句里这样左表users的行会全部保留只是在匹配时把右表局限在指定商品内。跑一遍这两条SQL对比结果行数你会直观看到同一个条件放ON和放WHERE的差异。这是一个值得反复练习的细节因为日常开发里这种差异造成的Bug最容易让你怀疑数据本身出了问题。4.2 第三套练子查询与自连接逻辑比语法更重要多表第三套通常开始上强度把子查询和自连接揉进来。子查询分三种IN后面跟一个结果集、EXISTS判断是否存在、标量子查询返回单个值。新手常用IN但EXISTS在大数据量下通常效率更高。自连接则是把同一张表当成两张表来用典型场景是“查每个员工的上级姓名”。-- 用自连接查每个用户的上级 SELECT e.user_id AS employee_id, m.user_name AS manager_name FROM users e LEFT JOIN users m ON e.manager_id m.user_id;这里users表自己被连接两次e是员工视角m是上级视角manager_id指向同表里的user_id。关键是区分两个别名否则你根本不知道谁是谁。自连接在练习题里出现的频率很高因为它能考察你对表别名的掌握程度。另一种经典场景是“查询每个类别里金额最高的记录”很像多表第四套的种子题很多人想到用GROUP BY但分组后无法直接取出整行这时子查询加关联就是更通用的解法。练习时可以先写IN版本再写EXISTS版本比较两种写法的执行计划你会发现同一个结果有不同的路径。4.3 第四套把窗口函数作为加分项多表第四套一般会加入窗口函数覆盖ROW_NUMBER()、RANK()、PARTITION BY等用来做分组内排名。窗口函数与聚合函数最大的区别是聚合函数会把多行压成一行窗口函数不会减少行数它只是把计算结果附加到每一行上。这个区别决定了它们适合解决不同的问题聚合适合“每个组汇总一个数”窗口适合“每一行都要保留原样但额外算排名”。-- 按城市分组对用户订单金额做排名 SELECT u.user_name, o.product_name, o.price, ROW_NUMBER() OVER (PARTITION BY u.city ORDER BY o.price DESC) AS city_rank FROM users u LEFT JOIN user_orders o ON u.user_id o.user_id;这里PARTITION BY u.city把数据按城市切成多个窗口ORDER BY o.price DESC在每个窗口内排序ROW_NUMBER()生成从1开始的连续序号。需要注意两点ROW_NUMBER()遇到同价位会强制区分名次不会并列users里没有订单的用户在窗口函数里也会保留一行只是price为 NULL。跑这条SQL时重点不是看排名对不对而是看没有订单的用户那几行还在不在。窗口函数不是独立语法它跟 JOIN 配合后的结果行数变化才是练习题真正想考的东西。5. SQL练习里最容易翻车的几个细节避坑5.1 WHERE 与 HAVING 的位置和执行顺序混淆现象写完GROUP BY后想把普通条件写在HAVING里结果报错或者结果不符合预期。原因是HAVING用于过滤分组后的组普通字段条件在分组前用WHERE更高效也更符合语义。解决办法是记住一句口诀WHERE先于GROUP BY执行HAVING后于GROUP BY执行。凡是单行条件写WHERE凡是聚合结果条件写HAVING。在练习题里把两者互换位置各跑一遍报错或错数本身就是最好的提醒。5.2 连表后的行数膨胀现象LEFT JOIN 之后结果行数比左表总行数还多。原因是右表里有重复的关联键导致左边一行匹配多条右边数据。这种问题在练习数据里不容易发现因为数据量小到真实业务里一行订单对应多个物流记录就会产生重复金额累加。解决方法是先分别查两边表的关联键重复情况。一个自查SQL写出来问题的原因就清楚了。5.3 GROUP BY 后 SELECT 字段书写不规范现象GROUP BY user_id然后直接SELECT user_id, user_name, SUM(amount)在某些数据库里报错某些数据库能跑但结果随机。原因是user_name不是分组键也不是聚合函数SQL 标准不允许这样选。解决办法只有两个把user_name也加进GROUP BY或者用ANY_VALUE之类的函数包裹它。练习时建议以严格模式为准不要因为当前数据库没报错就养成坏习惯。5.4 COUNT(*) 与 COUNT(列) 的 NULL 差异现象同一张表COUNT(*)和COUNT(amount)结果不同以为自己数据脏了。原因是COUNT(列)会忽略该列的 NULL 值COUNT(*)统计包含 NULL 在内的所有行。解决办法是想统计行数用COUNT(*)想统计某列非空值个数用COUNT(列)。这个细节在题目里经常被当成陷阱不需要背跑一段COUNT(*)和COUNT(amount)对比就能建立条件反射。5.5 DISTINCT 与 GROUP BY 的等价与不等价现象把SELECT DISTINCT city FROM users换成SELECT city FROM users GROUP BY city结果一样于是认为它们可以无条件互换。实际上DISTINCT只去重GROUP BY不仅去重还为聚合函数提供分组。如果你写SELECT city, COUNT(*) FROM users GROUP BY city;这不是去重是分组计数。DISTINCT无法替代聚合反过来GROUP BY做纯去重时又显得绕。解题时想清楚这次操作是“只要不重复的列表”还是“要按组统计”就不会纠结用哪个。6. 练完这八套题怎么验证基本功真的过关了刷完单表四套和多表四套别急着换下一批题。先把所有题目的答案从你的练习文件里删掉隔一天之后重新做一遍这次要求自己不看任何笔记直接手写SQL。如果第二次仍然在HAVING和WHERE的选择上犹豫说明第一轮只是记住了答案不是理解了语义需要把对应章节的题目再做一遍。这个“答案封存隔天重写”的做法比多刷一百道题更能检验真实水平。再进一步把同一套题在两种不同的数据库环境里各跑一遍。不同数据库对LIMIT的写法、对空串和 NULL 的处理有差异比如分页语法、日期函数名都不太一样。这种跨环境练习能帮你把SQL内核和数据库方言剥离开遇到不熟悉的数据库时至少知道哪里该查文档。第三个验证方法是对照执行计划。用EXPLAIN看语句是走了全表扫描还是索引范围扫描多表连接用了哪种连接算法。执行计划里的数字不一定精确但能暴露你有没有在大表上写WHERE时忽略索引。做题时多用EXPLAIN慢慢就会形成一种意识SQL 不光要结果对还要跑得快。最后建议按错题归因的方式把错误分类是语法错误、逻辑错误还是性能隐患。语法错误靠熟悉手册逻辑错误靠重做同类型题性能隐患靠改写语句和建索引。这样练完八套题后你手里会有一份自己的问题清单而不是简单的“能做题”或“不能做题”。我个人的习惯是每隔两三个月把这几套题翻出来重做一次每次都能发现上个月写语句时忽略的边界。这个动作比追新框架更能保住基本功。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →