资讯详情

资讯详情

SQL 100道基础练习题:从入门到实战的系统训练指南

上周有个刚转行的朋友问我“我把网上的SQL题刷了两百多道可一碰到需求稍微变一下的查询还是卡壳问题出在哪”我看了看他刷的题答案其实很简单——他刷的是“题”不是“方法”。这让我想起自己做MySQL教学和面试辅导时一直在用的那套“SQL 100道基础练习题”。它不追求偏题怪题而是把SQL日常开发里最常见的场景拆成一个个可递进的练习从单表过滤开始一步步走到聚合分组、多表连接、子查询、窗口函数最后落到DML、索引和事务。每一道题背后都有一个明确的考察点刷完不是让你“见过”而是让你以后拿到任何需求都能在脑子里先画出一条SQL的骨架。这篇文章把完整题目分类、表结构设计、部分典型题的精讲答案以及我自己在批改练习时看到的高频错误全部整理出来。适合三类人刚学完SQL语法但不知道怎么实战的初学者准备数据分析师或后端开发面试、想在短时间内系统过一遍SQL重点的求职者以及基础不牢、想回头补课的在职开发者。1. 这套题不是让你背答案的题目的设计逻辑市面上的SQL题很多但大多存在两个问题一是题目之间没有递进关系东一榔头西一棒槌二是过度堆砌复杂场景初学者连表结构都看不懂就直接被劝退。我最初整理这100道题时设定的原则只有三条覆盖日常工作高频语法、难度循序渐进、每道题都必须能讲清楚“为什么这样写”。1.1 为什么要从三张表开始很多教程喜欢用几十张表的大业务模型来出题但我个人强烈不建议新手这么做。SQL的核心思维是“对集合进行操作”这个思维在简单的表结构上更容易建立起来。这套题只用了三张表学生表、课程表、成绩表三者之间有外键关联既有单表操作又能做多表连接还能支持子查询和窗口函数的大部分练习场景。等你把这三张表玩透了换任何复杂的业务表结构本质上都是同样的套路。另一个现实原因是我在面试候选人和帮同事review代码时发现不少人写了大半年业务SQL看到“LEFT JOIN之后数据变多”“GROUP BY之后查不到非分组字段”这样的问题还是懵的。这些基础问题的根源恰恰是对单表过滤和连接逻辑的理解不够扎实。100道题里我特意把连接和子查询的部分占比拉高因为这两块才是实战中真正拉开差距的地方。1.2 100道题怎么分配整套题我分成了六个模块每个模块有明确的训练目标不是平均用力模块题目数量训练目标基础查询与过滤10题掌握SELECT核心子句、WHERE条件、LIKE/IN/BETWEEN/NULL判断聚合、分组与排序20题理解分组聚合逻辑能区分WHERE与HAVING多表连接25题掌握INNER JOIN/LEFT JOIN用法、自连接与连接条件子查询与派生表20题学会把复杂查询拆解成独立子问题窗口函数15题掌握排名、累计、分组内计算等进阶语法DML、视图、索引与事务10题不只会查还要能安全地增删改和建索引后面每个模块我都会有题目清单和精讲。你可以按顺序刷也可以根据面试目标跳着刷但我不建议打乱模块内部的顺序因为每道题考察的语法点都是在前面的基础上叠加的。2. 动手前先把三张表和测试数据建好练SQL的前提是你手上有一套干净的、自己完全掌控的数据。我见过有人直接用生产库练习结果一条UPDATE把线上数据改了这个风险无论如何都要避免。我建议你在自己的电脑上装一个MySQL 8.0用下面的脚本建库建表。2.1 环境准备装MySQL很简单直接到官网下载MySQL Community Server安装时记住root密码。如果你追求效率也可以用Dockerdocker run --name mysql100 -e MYSQL_ROOT_PASSWORDyourpassword -p 3306:3306 -d mysql:8.0客户端方面命令行效率太低我平常用Navicat或MySQL Workbench。Workbench免费但有时候界面卡顿Navicat需要破解经济条件允许的话建议支持正版。其实有一个更轻量的选择VS Code装一个MySQL插件日常练习完全够用重点是能用快捷键执行选中SQL比在终端一行行敲舒服得多。2.2 建表语句这100道题全部围绕学生选课成绩这个场景展开三张表的结构如下CREATE DATABASE IF NOT EXISTS sql100 DEFAULT CHARSET utf8mb4; USE sql100; DROP TABLE IF EXISTS scores; DROP TABLE IF EXISTS students; DROP TABLE IF EXISTS courses; CREATE TABLE students ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 学生ID, name VARCHAR(50) NOT NULL COMMENT 姓名, gender CHAR(1) DEFAULT M COMMENT 性别 M/F, class VARCHAR(20) COMMENT 班级, age INT COMMENT 年龄, enroll_date DATE COMMENT 入学日期 ) ENGINEInnoDB COMMENT学生表; CREATE TABLE courses ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 课程ID, course_name VARCHAR(100) NOT NULL COMMENT 课程名称, teacher VARCHAR(50) COMMENT 授课老师, credit DECIMAL(3,1) COMMENT 学分 ) ENGINEInnoDB COMMENT课程表; CREATE TABLE scores ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) COMMENT 成绩, exam_date DATE COMMENT 考试日期, CONSTRAINT fk_scores_student FOREIGN KEY (student_id) REFERENCES students(id), CONSTRAINT fk_scores_course FOREIGN KEY (course_id) REFERENCES courses(id) ) ENGINEInnoDB COMMENT成绩表;这里有几个细节想提醒你。第一所有字符集统一用utf8mb4避免中文乱码第二成绩字段用DECIMAL(5,2)而不是FLOAT因为浮点数在比较时会有精度问题这在后面做范围查询时会让结果变得莫名其妙第三外键约束在建表时就加上练习子查询时你才能理解为什么有些“无关联”的数据会被LEFT JOIN带出来。2.3 插入测试数据的几个小技巧建完表要插数据。为了让练习效果更好我建议你至少插入20名学生、6门课程、60条成绩记录并且要刻意制造一些“陷阱数据”比如某个学生没有任何选课记录、某门课程没有学生选、某些成绩为NULL、两个学生同姓名等。这些脏数据后面很多题目就是专门针对它们出的。INSERT INTO courses (course_name, teacher, credit) VALUES (语文, 王老师, 3.0), (数学, 李老师, 4.0), (英语, 张老师, 2.0), (物理, 李老师, 4.0), (化学, 赵老师, 3.0), (生物, 孙老师, 2.5); INSERT INTO students (name, gender, class, age, enroll_date) VALUES (张伟, M, 一班, 20, 2022-09-01), (王芳, F, 一班, 19, 2022-09-01), (李娜, F, 二班, 21, 2021-09-01), (刘洋, M, 二班, 20, 2021-09-01), (陈静, F, 三班, 22, 2020-09-01), (杨磊, M, 三班, 21, 2020-09-01), (赵敏, F, 一班, 20, 2022-09-01), (孙浩, M, 二班, 22, 2021-09-01);我特意让“张伟”和“赵敏”没有出现在scores表里后面题目会用到。成绩记录你可以自己造原则是保证每个学生至少选了1门课、最多选5门课并且把班级区分开方便做分组聚合练习。3. 模块一基础查询与过滤先把SELECT的手感练出来很多人觉得SELECT太简单但面试里最容易失分的恰恰是条件边界。什么叫“年龄大于20岁”如果age字段允许为NULL那么age 20这个条件会把NULL的记录过滤掉而很多人根本没意识到NULL的存在。这批基础题就是为了把这些细节磨透。题号题目考察点1查询学生表中所有学生的全部信息SELECT基本语法2查询所有学生的姓名和班级投影列3查询年龄大于20岁的学生姓名和年龄WHERE数值比较4查询一班且性别为F的学生AND多条件5查询姓名以“张”开头的学生LIKE模糊匹配6查询年龄在18到22岁之间的学生BETWEEN边界7查询班级为一班或二班的学生IN列表8查询没有填写年龄的学生IS NULL9查询学生表一共有多少条记录COUNT(*)10查询一共有哪些班级DISTINCT去重精讲两道比较容易出错的。第5题查询姓名以“张”开头的学生。SELECT * FROM students WHERE name LIKE 张%;考察点是LIKE通配符。%匹配任意多个字符_匹配单个字符。实际业务中经常遇到用户输入一个姓来搜索这里的索引利用率其实是很多人忽略的如果name字段有前缀索引或普通索引张%这种写法是可以走索引的但%张不行。你写SQL时心里要有个弦%放在最前面的模糊查询数据量一上来就容易慢。第8题查询没有填写年龄的学生。SELECT * FROM students WHERE age IS NULL;这个必须用IS NULL不能用 NULL。在SQL里NULL不是一个值而是“未知”的标记任何与NULL做比较的结果都是NULL也就是说age NULL永远不会为TRUE。我在面试中问过很多候选人至少有三分之一会写成age NULL或age 这两个都拿不到正确结果。如果真的想查空字符串才用age 但NULL与空字符串是两个完全不同的概念造数据时也要注意区分。第2题的变形——别名SELECT name AS 姓名, class AS 班级 FROM students;AS可以省略但建议保留可读性更好。注意中文别名在命令行客户端不需要加引号在部分框架里可能报错保险起见可以写成姓名。4. 模块二聚合分组与排序别一上来就写窗口函数进入第11题到第30题这是整套练习题的第一次卡人点。很多初学者能在SELECT和WHERE里游刃有余一遇到GROUP BY就蒙。核心原因是没有建立“分组后每一行代表一个组”这个认知。聚合函数的作用对象不是某一条记录而是一个分组分组里可能包含多条记录但最终只输出一行。题号题目考察点11查询每个班级有多少名学生GROUP BY COUNT12查询每个班级学生的平均年龄GROUP BY AVG13查询男女生各有多少人分组多字段14查询每门课程的平均分、最高分、最低分多个聚合函数15查询每个学生选了多少门课COUNT GROUP BY16查询平均分大于80分的课程HAVING17查询选课人数超过5人的课程IDHAVING COUNT18查询每个班级中年龄最大的学生年龄MAX GROUP BY19查询学生总数按班级降序排列ORDER BY 别名20查询每门课程的平均分并按照平均分从高到低排序聚合 排序21查询成绩总分最高的学生IDSUM GROUP BY22查询年龄在20岁以上的学生各班人数WHERE GROUP BY23查询每个班级男女生各多少人GROUP BY多列24查询班级人数不足3人的班级HAVING25查询每门课的选课人数没有学生选的课也要显示稍后用JOIN解决26查询各班级学生的年龄总和SUM27查询最小年龄、最大年龄和平均年龄聚合无分组28查询姓名重复的学生及重复次数GROUP BY HAVING29查询每个班级最早入学日期MIN30按年龄排序年龄相同按入学日期排序多字段排序第16题查询平均分大于80分的课程。SELECT course_id, AVG(score) AS avg_score FROM scores GROUP BY course_id HAVING AVG(score) 80;这道题十个人有八个人会写错错误版本是-- 错误示例 SELECT course_id, AVG(score) FROM scores WHERE AVG(score) 80 GROUP BY course_id;错在WHERE不能使用聚合函数。WHERE是在分组之前对原始行进行过滤的而聚合函数的结果在分组之后才产生。HAVING的作用就是过滤分组。一句话总结WHERE过滤行HAVING过滤组。另外HAVING中尽量直接写聚合函数不要依赖SELECT中的别名有些数据库不支持在HAVING里引用别名。第28题查询姓名重复的学生及重复次数。SELECT name, COUNT(*) AS cnt FROM students GROUP BY name HAVING COUNT(*) 1;这是面试常考的“查重复记录”原型题它能变形出很多类型查手机号重复、查订单号重复、查同一用户同一天的重复登录等。核心就是GROUP BY后COUNT大于1。5. 模块三多表连接SQL面试最常见的分水岭从第31题到第55题是整套题里含金量最高的部分。多表连接不是背语法而是要理解“连接到底在干什么”。我教新人的时候常用一个比喻JOIN就是把两张表按某种匹配规则横向拼接起来匹配上的行合在一起成为新表的一行匹配不上的行如果是LEFT JOIN左边表的行会保留右边表的字段填NULL。题号题目考察点31查询学生姓名和其选课的课程名称INNER JOIN三表32查询所有学生的选课情况没选课的学生也要显示LEFT JOIN33查询所有课程的学生选课情况没被选的课程也要显示RIGHT JOIN或LEFT JOIN换表34查询没有选任何课程的学生LEFT JOIN IS NULL35查询没有被任何学生选择的课程LEFT JOIN IS NULL36查询每个学生的选课数量JOIN GROUP BY37查询每门课程的选课学生人数同上38查询选修了“语文”课的学生的姓名JOIN WHERE39查询“李老师”教的所有课程以及选课人数JOIN 聚合40查询每个班级中每门课程的平均分JOIN GROUP BY多列41查询学生姓名、课程名、成绩并按成绩降序排列JOIN ORDER BY42查询成绩大于90分的学生姓名和课程名JOIN WHERE43查询每位同学的平均分并显示姓名JOIN GROUP BY44查询同班同名的学生自连接45查询年龄比同班平均年龄小的学生衍生表自连接46查询至少选了两门课的学生姓名JOIN HAVING47查询每个学生各科成绩都大于60分的记录聚合比较48查询所有学生中与“王芳”同班的学生自连接49查询每门课最高分对应的学生姓名连接 子查询50查询每个学生总分的排名先SUM后JOIN再排序51查询没学过“李老师”课程的学生NOT IN JOIN52查询选课门数超过3门的学生姓名HAVING53查询各科平均分都在80分以上的学生IDHAVING MIN54查询总成绩排名前3的学生姓名派生表55查询所有课程成绩都大于等于90分的学生NOT EXISTS第34题查询没有选任何课程的学生。SELECT s.id, s.name FROM students s LEFT JOIN scores sc ON s.id sc.student_id WHERE sc.id IS NULL;这道题是“LEFT JOIN 右表IS NULL”的经典用法比用NOT IN子查询在数据量大时性能更稳定。需要注意的是ON后面的连接条件到底该带什么、WHERE中过滤右表字段时会不会把左表记录也滤掉。如果你写了WHERE sc.student_id IS NULL结果一样但如果你不小心在WHERE里加了一个AND sc.score 0那NULL记录全部被过滤掉结果就变成0行了。这种错误我见过不止一次。第44题查询同班同名的学生。SELECT a.name, a.class FROM students a JOIN students b ON a.name b.name AND a.class b.class AND a.id b.id;自连接的要点是给同一张表起两个不同的别名关键条件要写a.id b.id否则每对重复姓名会和自己匹配一次。同理查询“比同班同学年龄大”就是ON a.class b.class AND a.age b.age这时候没有id 条件也会自动排除自己因为自己的年龄不可能大于自己。**第49题查询每门课最高分对应的学生姓名。**这道题很多人用GROUP BY MAX先查出最高分再JOIN成绩表。但要注意如果同一门课有两个学生同分且都是最高分分组JOIN会得到两行这其实是正确的。如果题目改成“每门课最高分的学生”语义上可以允许多个最高分。想取其中一个就需要窗口函数或ORDER BY LIMIT这是后话。6. 模块四子查询与派生表把复杂问题拆碎第56题到第75题是子查询专项。子查询的核心价值在于把“一步到位”写不出来的查询拆成“先查一个结果集再基于结果集查询”。写子查询不丢人可读性永远比“用极其复杂的方法强行避免子查询”更重要。真正需要优化的时候再考虑改成JOIN或EXISTS。题号题目考察点56查询与“张伟”同班级的学生IN子查询57查询年龄大于全校平均年龄的学生标量子查询58查询选了“语文”课的学生姓名IN子查询59查询没有选“数学”课的学生姓名NOT IN60查询成绩表中存在成绩记录的学生IDEXISTS61查询不存在成绩记录的学生姓名NOT EXISTS62查询每门课成绩最高的学生和成绩相关子查询63查询比所在班级平均年龄小的学生相关子查询64查询选修课程数超过平均选课门数的学生子查询HAVING65查询平均分最高的学生姓名派生表66查询每个班级年龄最小的学生相关子查询67查询总分超过班级平均总分的学生两次聚合的嵌套68查询每门课的选课人数并显示课程名派生表JOIN69查询所有学生中年龄最大的学生信息标量嵌套70查询成绩表中比某门课程平均分高的记录多个子查询71查询每个学生分数最高的课程相关子查询72查询成绩排名第二的学生的姓名和成绩ORDER BY LIMIT73查询选了所有课程的学生双重NOT EXISTS74查询学生姓名及各科成绩中最高分关联子查询75查询同一年级中比同班任一同学年龄都大的学生ALL子查询第57题查询年龄大于全校平均年龄的学生。SELECT name, age FROM students WHERE age (SELECT AVG(age) FROM students);这是一个标量子查询返回结果是单个值。注意AVG(age)如果students表是空表会返回NULL那么WHERE条件就恒为NULL查不出任何行。实际开发中统计报表脚本一定要处理这种空表场景否则结果会让你排查很久。第65题查询平均分最高的学生姓名。SELECT s.name, t.avg_score FROM students s JOIN ( SELECT student_id, AVG(score) AS avg_score FROM scores GROUP BY student_id ORDER BY avg_score DESC LIMIT 1 ) t ON s.id t.student_id;派生表就是FROM后面的子查询它必须有一个别名。MySQL在8.0之前对派生表的优化很差8.0之后有自动物化和合并优化性能改善明显。如果你还在用5.7遇到派生表慢的情况可以先STRAIGHT_JOIN或者改成临时表。第73题查询选了所有课程的学生。SELECT s.id, s.name FROM students s WHERE NOT EXISTS ( SELECT c.id FROM courses c WHERE NOT EXISTS ( SELECT 1 FROM scores sc WHERE sc.student_id s.id AND sc.course_id c.id ) );这题是双重否定选出那些“不存在任何一门课他没有选”的学生。很多初学者看到答案直接懵了但你只要掌握一个方法论把SQL从外层往内层读每个NOT EXISTS都翻译成“不存在……的记录”语境就清晰了。这也是数据库面试里出镜率极高的一道题值得反复研究。7. 模块五窗口函数与进阶语法面试加分项从第76题到第90题是窗口函数专项。MySQL 8.0之后开始支持窗口函数这算是目前面试中非常热门的考点。窗口函数和GROUP BY最大的区别是GROUP BY会折叠多行为一行而窗口函数不折叠行它在每一行上都保留原有的行同时增加一个计算列。题号题目考察点76按成绩从高到低给所有成绩记录排名ROW_NUMBER77查询每门课成绩的前三名RANK PARTITION BY78查询每个学生总分在中位数以上的记录PERCENT_RANK79查询每门课最高分与最低分的分差MAX/MIN OVER80查询每个班级学生按年龄排序的名次DENSE_RANK81查询每位同学在各科考试中的累计总分SUM OVER82查询每位同学成绩相比上一次考试的变化LAG83查询每位同学成绩相比下一次考试的变化LEAD84查询每个班级年龄最大的学生ROW_NUMBER PARTITION85查询每门课平均分排名AVG OVER86查询每个学生的选课数量在所有学生中的排名COUNT OVER ORDER BY87查询每个学生成绩最好的课程名称窗口函数 JOIN88查询每个班级按总分排名的前三名两层窗口89查询成绩表中每行与其所在课程平均分的差值AVG OVER90查询每个学生最近一次考试成绩ROW_NUMBER PARTITION ORDER BY第77题查询每门课成绩的前三名。SELECT course_id, student_id, score FROM ( SELECT course_id, student_id, score, ROW_NUMBER() OVER (PARTITION BY course_id ORDER BY score DESC) AS rn FROM scores ) t WHERE rn 3;这里用ROW_NUMBER而不是DENSE_RANK两者的区别是ROW_NUMBER对并列成绩也会给出不同的序号1、2、3、4而DENSE_RANK会为相同成绩分配相同名次并且下一名是紧接着的数字1、1、2。如果你希望“成绩相同算并列”就用DENSE_RANK。我建议你先把窗口函数中的三个排序函数都练一遍然后回答一下它们之间的差异这道面试送分题别丢。第82题查询每位同学成绩相比上一次考试的变化。SELECT student_id, exam_date, score, LAG(score) OVER (PARTITION BY student_id ORDER BY exam_date) AS prev_score, score - LAG(score) OVER (PARTITION BY student_id ORDER BY exam_date) AS diff FROM scores;LAG取同一分组内前一行LEAD取后一行。PARTITION BY指定了分组范围ORDER BY指定了组内的排序规则这两者缺一不可。很多人在学习窗口函数时最大的误区是忘记写PARTITION BY结果导致全表变成一个窗口算出来的值完全不是想要的分组内结果。窗口函数的执行顺序要记住一个口诀窗口函数是在WHERE和GROUP BY之后执行的所以你不能在WHERE里对窗口函数的结果进行过滤这就是为什么第77题需要先查窗口函数再包一层子查询。如果你在MySQL 8.0里写了WHERE rn 3会直接报错因为WHERE子句根本看不到窗口函数生成的列。8. 模块六DML、视图、索引与事务不只会查还要会改第91题到第100题虽然只有10道但它们保证了这套练习的完整性。很多学习SQL的人只会SELECT一到要写UPDATE和DELETE就畏手畏脚或者乱写一气导致生产事故。我个人认为用可控的练习数据把DML、视图、索引和事务都过一遍是非常必要的。题号题目考察点91向students表插入一条新学生记录INSERT基础92将“张伟”的班级改为一班UPDATE单表93删除年龄大于25且没有成绩记录的学生DELETE JOIN94创建一个视图显示学生姓名、课程名、成绩CREATE VIEW95从视图中查询平均分最高的学生视图复用96给scores表的course_id字段创建索引CREATE INDEX97用EXPLAIN查看一条查询语句的执行计划EXPLAIN98开启一个事务插入一条成绩记录并回滚BEGIN/ROLLBACK99开启一个事务插入一条记录并提交COMMIT100查询当前数据库中的所有表和视图SHOW TABLES第93题删除年龄大于25且没有成绩记录的学生。DELETE s FROM students s LEFT JOIN scores sc ON s.id sc.student_id WHERE s.age 25 AND sc.id IS NULL;MySQL支持在DELETE中直接JOIN这是很多人不知道的语法。执行前可以先改写成SELECT看看会命中哪些行这是一个好习惯SELECT s.* FROM students s LEFT JOIN scores sc ON s.id sc.student_id WHERE s.age 25 AND sc.id IS NULL;第97题用EXPLAIN查看一条查询语句的执行计划。EXPLAIN SELECT s.name, c.course_name, sc.score FROM scores sc JOIN students s ON sc.student_id s.id JOIN courses c ON sc.course_id c.id WHERE sc.score 90;看EXPLAIN主要关注type列和rows列理论上type至少要到ref如果出现ALL全表扫描且数据量大就要考虑加索引。还要注意Extra列里有没有Using filesort和Using temporary这两个都是SQL优化的重点信号。做这道题的时候可以试一下把scores表的course_id索引先删掉再EXPLAIN你会发现rows列的预估扫描行数明显上升这样就直观理解索引的作用了。第98题和第99题事务回滚与提交。START TRANSACTION; INSERT INTO students (name, gender, class, age) VALUES (测试学生, F, 一班, 18); ROLLBACK; START TRANSACTION; INSERT INTO students (name, gender, class, age) VALUES (保留学生, M, 二班, 19); COMMIT;事务我建议用InnoDB练习MyISAM不支持事务。执行ROLLBACK或COMMIT之后再SELECT一下你能直观看到哪些操作被保留、哪些被撤销。别看这两题简单对新手理解“原子性”和“持久性”非常有帮助。实际工作中如果你在存储过程或者脚本里做了多步数据修改一定记得包在事务里并且在异常分支做ROLLBACK这是避免脏数据的最后一道防线。9. 刷完这100道题之后我实测容易翻车的五个点这套题我在带新人、帮朋友突击面试时反复用了很多轮最后总结几个绝大多数人都会踩的坑提前写给你省得你走弯路。**第一函数依赖问题。**MySQL 5.7和8.0中SELECT后面出现的非聚合字段如果不是GROUP BY字段默认是允许的5.7只在严格模式下才报错但查出来的值来自分组中的哪一行是不确定的。第16题如果写成SELECT course_id, course_name, AVG(score)且GROUP BY只写了course_idcourse_name可能随便拿一行逻辑上是错的。写SQL时自己心里要有数别依赖这个“默认行为”。**第二JOIN后面的ON和WHERE的边界。**第34题已经演示过ON负责连接条件WHERE负责结果集过滤。如果你想在LEFT JOIN后把右边为NULL的行保留过滤条件必须写在ON里而不是WHERE里。同理在内连接中ON和WHERE的最终结果等价但语义不同别混着用。**第三COUNT(1)还是COUNT(*)。**这两者在MySQL中性能几乎没有差别但COUNT(某字段)会忽略字段值为NULL的行而COUNT(*)不会。第9题和第28题用COUNT(*)第15题用COUNT(course_id)都能用但如果你统计的是“有多少人选了课”而课程ID恰好有NULL两个结果就不一样了。做题时先问自己到底想数行数还是数该字段非NULL的个数。**第四LIMIT与分页的性能坑。**第72题查第二名用ORDER BY score DESC LIMIT 1, 1数据量小没问题但数据量大时LIMIT偏移量越深越慢。比如LIMIT 100000, 10数据库要先扫过前10万行再取数据。实际业务中可以用“上一页最后一条记录的ID”来做条件过滤替代深分页。这也是慢SQL优化的常见场景。**第五慢SQL优化别只盯着索引看。**热搜词里“慢sql优化 explain主要看哪些信息”热度很高我在批改练习时也提醒大家explain里的type列出现ALL、Extra里出现Using temporary或Using filesort都要警惕。但优化顺序永远是先看SQL逻辑是否扫了非必要的数据再看索引是否生效最后才考虑改表结构和缓存。很多新人事先给所有字段都加索引反而导致写操作变慢这是典型的过度优化。最后给你一个刷题建议这套题我自己的使用习惯是每10题一组刷完一组就停下来不看答案重新在空的数据库里自己造一套不同的测试数据再把这10道题重新写一遍。这样做的目的是强迫自己理解每一道题背后的语法逻辑和适用场景而不是把答案背下来。如果你是在准备面试我特别建议把第28题、第34题、第49题、第65题、第77题这几道题吃透因为它们是“查重复、查缺失、查最值、查分组TopN、查排名”五大面试原型的代表面试官出题基本都是从这些原型变形出来的。把这五类题目背后的写法练到不用想就能写出来比你盲目刷几百道零散题有用得多。等100道题全部过完你可以试着把题目里的“一、二、三班”换成你自己的业务订单表、用户表、商品表重新套一遍这套练习逻辑。做到那一步SQL对你来说就不再是背语法而是真正的工具了。
觉得有用,分享给同行:

为您的企业打造数字门面

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

立即咨询 →