十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL经典50题:从基础查询到窗口函数,覆盖SQL面试核心场景

MySQL经典50题:从基础查询到窗口函数,覆盖SQL面试核心场景 刷过这套“MySQL经典50题”基本就拿下了面试中80%的SQL场景以前在社区里总看到有人问MySQL到底怎么学才不算白学或者说学完了增删改查下一步该拿什么练手如果你也有这个困惑那我建议你直接去刷那套流传了很多年的“MySQL练习题经典50题”。这套题在网上已经流行了快十年各个版本满天飞但核心就是那几张表学生表、课程表、教师表、成绩表。别小看这套题从最简单的SELECT到让人头疼的关联子查询、分组排序取TopN再到行列转换和函数应用它几乎覆盖了日常开发、数据分析、后端面试里90%的SQL考点。我自己带过的实习生、社招候选人里凡是能把50题顺滑刷完、每一道都能讲清思路的后续看公司业务代码基本没有任何障碍。这篇文章不打算把50题答案原封不动抄一遍——网上能搜到一堆而且很多答案写得啰嗦甚至跑不通。我更想做的是把这套题背后真正值得花时间的6个方向拆开讲清楚每一类题目在考什么、常见的解题套路长什么样、哪些答案写法很丑陋但能跑、哪些写法性能更优但难懂。如果你正在准备面试或者刚入门SQL不知道从哪里突破这篇文章可以帮你省下不少弯路。1. 这50题到底在练什么一张成绩表背后的SQL能力地图先说说这套题为什么经久不衰。因为它用最简单的业务场景把SQL的核心操作几乎全串起来了。很多人买了几百页的《MySQL必知必会》看完还是不会写复杂查询原因就是缺乏一个“带着问题去检索脑子里语法”的场景。50题恰恰就是一个很好的检索场景库。1.1 题目涉及的核心技术点我梳理了一下这50题的考点分布大概是这样基础查询与过滤SELECT、WHERE、LIKE、IN、BETWEEN、DISTINCT这类送分题大概占10道左右。排序与分组统计ORDER BY、GROUP BY、HAVING、聚合函数COUNT/SUM/AVG/MAX/MIN大概15道。多表连接INNER JOIN、LEFT JOIN、自连接、非等值连接大概12道。子查询IN子查询、EXISTS子查询、关联子查询、标量子查询大概8道。高级函数与逻辑CASE WHEN、DATE函数、字符串函数、IFNULL处理大概5道。这个分布说明一个事50题的核心并不是“难题”而是“细题”。它考的不是你会不会写一个惊世骇俗的复杂SQL而是你是否能把不同语法组合起来解决实际问题。1.2 为什么这张“学生成绩表”能当试金石因为学生-课程-成绩这个模型足够简单不需要懂业务背景每个人都能理解“查平均分大于60的学生”是什么意思。但也因为足够简单它便于出题人把各种SQL特性叠加进去。比如第1题通常很简单“查询所有学生的信息”。很多人觉得这题有什么好刷的但实际上等到第14题“查询各科成绩前两名的记录”时同一个库、同一套数据难度直接跳了一个台阶。这时候你会发现单纯的SELECT已经不够用了至少需要窗口函数ROW_NUMBER或者复杂的自关联对比。我的建议是不要按顺序麻木地刷而是按难度梯度刷。先做送分题建立信心再集中攻克连接和子查询最后再碰窗口函数和变量解法。这样脑子里的SQL知识是“成块”的而不只是一条条孤立的习题。2. 前置准备建表、造数以及刷题前必须想明白的一件事很多人下载了一份50题的SQL脚本直接粘贴到Navicat里执行结果报错一片。别急这很有可能是你的MySQL版本和脚本里的语法对不上也有可能是SQL_MODE的问题。2.1 推荐环境从零建库建表到灌入模拟数据我推荐你这么做而不是直接跑别人写好的脚本新建一个数据库建议命名practice字符集用utf8mb4排序规则用utf8mb4_general_ci。手动创建四张表student、course、teacher、score。字段不用多够用就行。下面是我个人常用的建表脚本你可以直接复制CREATE TABLE student ( sid INT PRIMARY KEY COMMENT 学号, sname VARCHAR(20) NOT NULL COMMENT 姓名, sage DATETIME COMMENT 出生日期, ssex VARCHAR(10) COMMENT 性别 ); CREATE TABLE course ( cid INT PRIMARY KEY COMMENT 课程编号, cname VARCHAR(20) NOT NULL COMMENT 课程名称, tid INT NOT NULL COMMENT 教师编号 ); CREATE TABLE teacher ( tid INT PRIMARY KEY COMMENT 教师编号, tname VARCHAR(20) NOT NULL COMMENT 教师姓名 ); CREATE TABLE score ( sid INT NOT NULL COMMENT 学号, cid INT NOT NULL COMMENT 课程编号, score DECIMAL(5,2) COMMENT 分数, PRIMARY KEY (sid, cid) );建表之后再插入个十几条模拟数据。为什么我不直接给你一份完整的INSERT因为造数这个过程本身就能帮你理解题意。比如你想查“平均分大于60的学生”如果你连数据分布都不清楚写出来的SQL很可能只是“碰巧过了用例”。2.2 一个容易被忽略的坑表名和字段不要用保留字很多人的50题MySQL脚本跑不过问题出在表名或字段名撞了保留字。比如有人把“成绩”表的字段写成desc把学生表命名为order这在MySQL里会直接语法报错。如果你看到别人的脚本里有这种命名要么整体反引号括起来要么老老实实改掉。我的习惯是所有表名用单数名词所有字段名带上前缀。比如学生表student学号字段sid避免用id这种到处撞车的名字也避免使用name这种过于泛化的字段。这套习惯在刷题阶段也许看不出差别但等你到了公司写真实业务SQL就知道有多重要了。3. 先从几道“送分题”看基本功查重、排序、最值别觉得送分题没营养。我见过很多工作两三年的人写GROUP BY还是动不动就报ONLY_FULL_GROUP_BY错误连错误原因都说不清楚。送分题就是用来检验这些基本功的。3.1 查重与去重DISTINCT还是GROUP BY50题里经常有“查询所有教师的名字不能重复”。很多新手第一反应是SELECT DISTINCT tname FROM teacher;这个写法没问题但如果题目改成“查询每门课程的最高分”DISTINCT就派不上用场了你得学会SELECT cid, MAX(score) FROM score GROUP BY cid;说白了DISTINCT适合“去重后只看一列”的场景而GROUP BY是“去重后还要聚合其他列”的场景。这两者的界限很多人刷完50题也没搞明白因为答案都能跑通但应用场景完全不同。3.2 排序题别只会ORDER BY DESC有一类题目是“查询平均成绩大于60分的学生的学号和平均成绩”。这题看着简单但它同时考察了三个点GROUP BY、HAVING、ROUND以及排序。我的参考写法SELECT sid, ROUND(AVG(score), 2) AS avg_score FROM score GROUP BY sid HAVING AVG(score) 60 ORDER BY avg_score DESC;这道题最典型的一个错误是把过滤条件写成WHERE AVG(score) 60。很多人不知道WHERE在分组之前生效压根没法直接使用聚合函数。聚合函数条件的过滤必须放在HAVING里这是SQL的执行顺序决定的FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。别觉得这是小事我面试别人的时候碰到这道题10个人里至少有4个会把WHERE和HAVING搞混。50题里凡是涉及“平均”“最大”“最少”这些词的几乎都可以用这条顺序来推导。3.3 隐藏的“日期函数”考点有一道经典题是“查询1990年出生的学生名单”。很多人看到sage是DATETIME类型就想着用WHERE sage 1990-01-01那当然查不到所有数据。正确思路是用日期函数提取年份SELECT * FROM student WHERE YEAR(sage) 1990;如果想要通用性更强可以写成SELECT * FROM student WHERE sage 1990-01-01 AND sage 1991-01-01;第二种写法对索引更友好虽然在这个练习的数据量下无所谓但是一旦放到生产环境YEAR(sage)会让索引直接失效这一点值得养成习惯。50题里日期类题目不多但如果没有这道题估计很多人永远意识不到日期函数和索引失效之间的关系。4. 真正拉开差距的是这几类“算分题”如果说前面那些题是热身那从“自连接”开始50题的价值才真正体现出来。这也是很多人刷到一半就放弃的地方——答案看得懂但下次遇到类似的题还是写不出来。4.1 自连接同一张表自己和自己比有一道题是“查询每门课程成绩都大于80分的学生”。常规想法是先按学生分组再用MIN(score) 80去判断。但我见过不少答案用自连接或者子查询来实现这说明SQL的解法往往不止一种。我更推荐这种思路清晰的分组写法SELECT sid FROM score GROUP BY sid HAVING MIN(score) 80;这道题的“坑点”在于很多人第一反应是找“每门课都大于80”然后想着“怎么排除那些有低于80分课程的学生”于是写出了NOT IN (SELECT sid FROM score WHERE score 80)。这个写法也能得出正确答案而且更容易理解。所以说SQL的解题思路不是唯一的你要做的是在“好懂”和“高效”之间找到适合自己的平衡点。4.2 关联子查询查“比同课程平均分高”的学生50题里有一道经典中的经典“查询每门课程成绩比该课程平均分高的学生记录”。我当年第一次做这题时苦思冥想也写不出来后来才意识到这要用关联子查询。参考写法SELECT s.* FROM score s WHERE s.score ( SELECT AVG(score) FROM score WHERE cid s.cid );关键在于内层子查询里的WHERE cid s.cid它让子查询和外面每一行产生了关联因此被称为“关联子查询”。对于外层的每一行成绩记录系统都会去算一遍对应课程的平均分然后做比较。这种写法在面试里非常讨喜因为面试官一看就知道你理解“行与行之间的关联”而不仅仅是会套模板。而且理解了这个之后再看“查找比张三成绩高的学生”、“查找各科最高分”这些题思路都会清爽很多。4.3 TopN问题分组取前两名该怎么写50题里的小boss是“查询各科成绩前两名”。这个题在不同MySQL版本下解法完全不同MySQL 8.0及以上直接上窗口函数ROW_NUMBER() OVER (PARTITION BY cid ORDER BY score DESC)简单粗暴。MySQL 5.7及以下没有窗口函数只能用“自连接计数”的思路。5.7时代的经典写法SELECT s.cid, s.sid, s.score FROM score s WHERE ( SELECT COUNT(*) FROM score WHERE cid s.cid AND score s.score ) 2 ORDER BY s.cid, s.score DESC;这个写法的含义是对每条成绩记录统计同课程中“分数比我高”的人数如果人数小于2那我就是前两名。这个思路不仅适用于Top2也适用于TopN是经典中的经典。如果你用MySQL 8.0强烈建议直接学窗口函数因为不仅写法简单性能也更好。很多人问“窗口函数要不要学”我的答案很明确一定要学但至少得先知道旧写法是怎么工作的。因为很多公司的老项目还在用5.7你去了要能看懂线上代码。4.4 行列转换CASE WHEN配合聚合函数的妙用还有一类题目会让你用一行行的成绩表输出一个“学生每门课分数”的横表。这需要结合CASE WHEN与聚合函数。SELECT sid, MAX(CASE WHEN cid 01 THEN score END) AS 语文, MAX(CASE WHEN cid 02 THEN score END) AS 数学, MAX(CASE WHEN cid 03 THEN score END) AS 英语 FROM score GROUP BY sid;这里为什么要套MAX因为CASE WHEN在分组后会返回多条记录如果不套聚合函数MySQL会随机取一条逻辑不严谨。套上MAX之后非匹配行返回NULL匹配行返回分数取最大值自然就是正确的分数。这个技巧在报表统计里极其常见值得花点时间亲手敲一遍。5. 踩坑实录这50题里最常见的三个误区刷题刷多了你会发现大家犯的错误其实高度一致。这里我把最常见的三个坑写出来也算是给后来者提个醒。5.1 误区一在WHERE里用聚合函数刚才已经提过WHERE不能直接接聚合函数。但有些人换了种写法以为HAVING和WHERE可以随意互换。其实它们差别很大场景WHEREHAVING过滤时机分组前分组后能否用聚合函数不能可以能否用别名过滤部分情况可以但非标准可以直接建议判断方式很简单看到题目里有“平均”“总计”“每个”这类词先想清楚是先分组还是先过滤再决定用哪个关键字。5.2 误区二COUNT(字段)和COUNT(*)傻傻分不清有一道题是“统计每门课程的学生人数”。这题看似简单但如果你写出COUNT(score字段)当这个字段存在NULL时人数就会偏少因为COUNT(字段)会自动忽略NULL值。稳妥的做法SELECT cid, COUNT(*) FROM score GROUP BY cid;同理统计某个学生的选课门数时也应该用COUNT(*)因为成绩为NULL并不代表他没选课。别小看这个差异在实际业务中统计订单数、统计用户数时都容易踩这个坑。5.3 误区三忽略NULL对查询结果的影响再举个例子如果某张表里没有某学生的成绩记录用NOT IN去做排除时只要子查询结果里有NULLNOT IN会直接返回空结果。这是SQL里一个著名的陷阱。比如你想查“没选‘语文’课的学生”如果用SELECT * FROM student WHERE sid NOT IN ( SELECT sid FROM score WHERE cid 01 );一旦子查询结果集中包含NULL整体结果就会出问题。改用NOT EXISTS会更稳SELECT * FROM student s WHERE NOT EXISTS ( SELECT 1 FROM score sc WHERE sc.sid s.sid AND sc.cid 01 );刷50题的过程中你会反复碰到NULL相关的问题。我的建议是把每一道涉及“没有”“不等于”“排除”的题都顺手用NOT EXISTS和NOT IN各写一遍感受一下差异。这才叫有效刷题。6. 刷完50题之后怎么把SQL能力再往上提一档50题刷完只能说你的“语法基础”过关了。但真实业务场景比练习题恶劣得多数据量动辄几千万行、表结构乱七八糟、索引缺失、查询慢到怀疑人生。所以很多人问我“刷完50题接下来学什么”我的答案通常是学习怎么让SQL跑得快。6.1 用EXPLAIN审视你的每一个查询还记得那些在大表上跑得飞快的查询吗80%的秘诀都在索引。50题里的表数据量可能只有几十行建不建索引根本感觉不到差别。但在生产环境一条全表扫描可能让整个服务超时。我建议你刷完50题后随便挑几条复杂查询加上EXPLAIN看看执行计划。重点观察type列如果看到ALL说明全表扫描看到range或ref说明索引起效了看到const说明走了主键或唯一索引。这些都是实打实的工作经验面试时能主动讲出EXPLAIN的输出含义比背概念加分得多。6.2 把练习题改成“公司真实业务版”我自己教人的时候喜欢让人做一件事把50题的业务场景替换成公司常见的“订单表用户表商品表”然后重新想一遍。举个例子把“查询平均成绩大于60分的学生”改成“查询平均客单价高于100元的用户”把“查询各科前两名”改成“查询各品类销量前两名的商品”。你会发现题目本质上没变但需求感知完全不同。这种能力迁移比多做100道新题都管用。6.3 顺手把存储过程、变量、视图也摸一遍50题里虽然没有太多存储过程但如果你用MySQL练习环境完全可以尝试把某一类重复查询封装成存储过程或视图。比如写一个存储过程输入课程编号输出该课程成绩排名。这个过程会逼着你理解“变量”“游标”“流程控制”这些进阶概念。我见过不止一个候选人SQL基础题答得很漂亮但一问到“如果这个查询每天要跑一次你会怎么设计”就卡壳。存储过程和视图就是你从“写SQL”迈向“设计SQL”的一小步。写在最后MySQL的经典50题我前前后后刷过至少三遍第一遍是学习时对照答案抄第二遍是带实习生时一道题一道题讲思路第三遍是准备面试资料时把每道题都从5.7和8.0两个版本各写了一遍。每一遍都有新的收获尤其是窗口函数普及之后很多老答案的写法已经可以被替代但底层思路永远是那套先过滤、再分组、再聚合、再排序遇到复杂需求就拆成子查询或自关联。如果你正好刚开始刷这套题我的建议很简单不要背答案也不要急着找“最快写法”。先自己闷头写写不出来再看答案看懂了之后关掉答案再写一遍。等你把50题都能不看答案完整写出来每道题还能跟别人讲清楚为什么这么写那你SQL这块的基本功就真的扎实了。
返回列表