你以为你会写 SELECT,其实你大概率只是在需要数据的时候复制粘贴一条 SELECT *。这个感受在我重新刷 SQLZoo 的 SELECT 基础练习时特别明显。作为一个平时经常和 MySQL 打交道的人,我一直觉得自己写 SQL 没什么问题,可真到 SQLZoo 上一题一题过的时候,才发现光是 SELECT 基础就能拆出那么多平时压根不会注意的细节。下面这份练习记录,就是我完整刷一遍 SQLZoo 上 MySQL select 基础的过程:为什么选这个平台、练习题怎么拆、哪些地方反复出错、以及刷完之后怎么在本地 MySQL 环境里重新验证一遍。适合刚学 SQL、想打牢 select 基础的读者,也适合那些嘴上说"会 select",但一写复杂条件就翻车的人。
1. 为什么拿 SQLZoo 练 SELECT:我的选型思路
1.1 浏览器即开即用,数据真实有趣
先聊一个问题:为什么我推荐拿 SQLZoo 练手,而不是一上来就装 MySQL。很多新手学 SQL 的第一个门槛,根本不是语法,而是环境搭建。按教程去下载 MySQL、配置服务、设置密码,中间任何一个环节出问题,一晚上就没了。SQLZoo 最大的优势是零安装,打开官网就能写 SQL,它内置了两张表,一张是 world 表,记录全球国家的名称、大洲、面积、人口、GDP,另一张是 nobel 表,记录历年的诺贝尔奖数据。这两张表不是无意义的 student、teacher 演示数据,而是真实世界的公开数据,查起来有代入感。
这种"即开即用"对刷题特别重要。学习 SQL 的核心是手要勤,而不是等所有环境都完美配置好再开始。你只需要写一句 select,点运行,系统就会执行并展示结果。对了就进入下一题,错了会看到错误信息。这种即时反馈,是保持学习动力最关键的东西。我自己在 SQLZoo 上从 select basics 开始刷,十分钟就能进入状态,因为每一题都有明确目标,不会像对着空数据库那样不知道该干嘛。
1.2 和"看视频、自己建表"相比,它赢在反馈
市面上学 SQL 的路子无非这么几条:看视频教程、买书、自己建表练习、直接在线上刷题平台练。我整理一个表格对比:
| 学习方式 | 优点 | 缺点 |
|---|---|---|
| 看视频/看书 | 系统性强,能听懂概念 | 听懂了和会写是两回事,容易眼高手低 |
| 自己建表练习 | 贴近实际业务,自由度高 | 需要自己设计场景和题目,大部分人坚持不下来 |
| 直接上 SQLZoo | 有现成数据和题目,从易到难,一键判题 | 题目场景偏基础,和复杂业务还有距离 |
| 只看面试题 | 目标明确,贴近求职 | 缺乏系统训练,容易被题目吓退 |
比较下来你会发现,SQLZoo 适合作为"第一遍刷语法"的地方。它把 SELECT 基础拆成了很小的关卡,每一关只做一件小事。比如先让你查一个国家的面积,再让你同时查好几个国家;先让你用等于号精确匹配,再让你用 LIKE 做模糊匹配。这种循序渐进的设计在心理上很友好,不会一上来就甩给你十几张表让你 JOIN。
还有一点很关键:它自带答案比较。自己建表练的时候,你写了 SQL 都不知道对不对,还得自己去数结果;在 SQLZoo 上,错了系统会显示 Expected result 和 Your result 的对比,排错过程本身就是学习过程。很多人在这一步养成了"先自己读结果,再看哪里不对"的习惯,这个习惯比背语法值钱得多。
1.3 SELECT 基础为什么值得单独刷一遍
有人觉得,select * from table 早就会了,刷基础是不是浪费。我的看法是,SELECT 是整个 SQL 的地基,后面的 JOIN、子查询、GROUP BY 都是在这块地基上盖起来的。你 JOIN 写不明白,很多时候不是 JOIN 本身难,而是过滤条件和表关系没想清楚;你子查询看不懂,很多时候是 SELECT 的作用范围没搞明白。刷 SELECT 基础,表面上是练语法,实际上是在练"用条件表达需求"的能力。
SQLZoo 的基础题做下来,覆盖了列选择、WHERE 过滤、IN、BETWEEN、LIKE、DISTINCT、ORDER BY、LIMIT、别名这些点。单独看都很简单,但组合起来最容易出问题。所以这一遍基础,我当作给自己做一次语法体检:哪些知识点以为自己会,其实根本经不起问。
2. SQLZoo 的 SELECT 基础关卡:题目和语法一起拆
2.1 最简单的查询:选列 + 条件过滤
SQLZoo 的 SELECT basics 模块,上来最经典的一题是查出法国的人口:
SELECT population FROM world WHERE name = 'France'这题看起来简单,但它包含了一个最核心的理解:SELECT 后面的列,是你想看到的字段;WHERE 后面的条件,是决定哪些行满足要求。WHERE 就是过滤器,它一行一行扫描,条件为真的留下,为假的离开。很多新手会困惑"为什么是 name = 'France',而不是直接查 population",其实就是没把"选列"和"过滤行"两个动作分开。
紧接着它会出一个 IN 条件的题,显示瑞典、挪威和丹麦三个国家的名称和人口:
SELECT name, population FROM world WHERE name IN ('Sweden', 'Norway', 'Denmark')IN 解决的是"多个候选值"的需求。你当然可以用 OR 串联,写起来会长很多,可读性也差。这道题的另一个意义是让你明白,SELECT 后面可以放多个列,列的顺序就是结果表的列顺序。我建议做题时养成习惯,第一眼先问自己:我要看哪些列?再问:条件是啥?先后顺序对了,SQL 就成功了一半。
2.2 进阶条件:IN、BETWEEN、LIKE 的实际用法
再往下,SQLZoo 会让你用 BETWEEN 找面积在 20 万到 25 万平方公里之间的国家:
SELECT name, area FROM world WHERE area BETWEEN 200000 AND 250000BETWEEN 是闭区间,包含两端边界值。也就是说,面积等于 200000 和等于 250000 的国家都会被查出来。这一点和有些编程语言里的 range 左闭右开不一样,写的时候要特别留意。BETWEEN 适合连续数值区间,如果查的是离散的几个值,用 IN 更合适。
LIKE 和通配符也是重点。题目会让你找出名称以 'Al' 开头的国家:
SELECT name FROM world WHERE name LIKE 'Al%'这里 % 表示任意长度的任意字符,所以 'Al%' 能匹配 Albania、Algeria 这类名字。如果把 % 放在两边,写成 '%United%',就能匹配名称任意位置包含 United 的国家,比如 United States、United Kingdom。还有一个下划线通配符,它只匹配一个字符,比如 'C' 能匹配以 C 开头、后面只有一个字符的字符串。LIKE 是最朴素的模糊搜索实现方式,把通配符逻辑吃透,后面学正则表达式也不吃力。
2.3 控制结果形态:DISTINCT、ORDER BY、LIMIT 与别名
SELECT 基础练到中间,开始涉及结果形态的控制。比如要你用 DISTINCT 查看 world 表里有哪些大洲:
SELECT DISTINCT continent FROM worldDISTINCT 的作用是去重。很多人刚开始会忽略它,直接 select continent from world,结果返回几百行,里面重复非常多。这个习惯如果带到真实报表里,会出现大量"看起来一样"的行,review 的时候又费眼又容易出错。
ORDER BY 排序是更常见的需求,比如按人口倒序取出前几名的国家:
SELECT name, population FROM world ORDER BY population DESC LIMIT 5DESC 表示从大到小,ASC 是默认从小到大。LIMIT 5 表示只返回前 5 行。这三者配合起来的意图很清晰:先对结果集排序,再截取前 N 行。需要注意执行顺序是 FROM 先确定表,WHERE 再过滤,SELECT 选列,ORDER BY 排序,LIMIT 最后截断。这个顺序概念在后面的分页查询里会反复用到。
还有一个容易被忽略但很实用的别名语法:
SELECT name AS country, population AS people FROM worldAS 是给列起别名,别名会直接显示在结果表头上。实际工作中写报表、做接口返回字段映射时,别名几乎是必须的。理解别名,也是后面子查询和 JOIN 的基础,因为多表查询时列名容易冲突,靠别名区分是最常见的手段。
2.4 澄清一个概念:select 不是函数,是查询语句
搜"select函数"的时候,能看到很多人把它当成某种函数来理解,其实 SELECT 在 MySQL 里不是函数,而是一条查询语句的开头。真正有"函数"性质的是 COUNT、SUM、MAX 这类聚合函数,或者 DATE_FORMAT 这类格式化函数。在 SQLZoo 的题目页面上,你输入的重点不是"调用某个 select 函数",而是把一条完整的 select 语句组织好:选哪些列、从哪张表、用什么条件过滤。理解这一点能帮你少走很多弯路,尤其是看资料的时候,不会被"select 函数"这类说法带偏。
3. 练习中踩过的坑:从"答案错误"到"彻底理解"
3.1 多条件没加括号,逻辑跑偏到意想不到的地方
我第一次刷到需要同时使用 AND、OR 的组合条件时,写出来的语句是这样的:
SELECT name, population FROM world WHERE name = 'France' OR name = 'Germany' AND population > 80000000本意是"法国,或者德国且人口大于 8000 万",但实际执行的逻辑却是:
WHERE name = 'France' OR (name = 'Germany' AND population > 80000000)因为 SQL 里 AND 的优先级高于 OR。如果目标只是"法国或德国,并且两者人口都大于 8000 万",应该这样写:
SELECT name, population FROM world WHERE (name = 'France' OR name = 'Germany') AND population > 80000000这个坑在真实业务里极其常见。比如查"VIP 用户或最近 30 天有购买记录的用户,且状态正常",如果 OR 前后不加括号,查询范围会比预期大很多,统计口径直接出错。我现在的习惯是:只要 WHERE 里同时出现 AND 和 OR,一律给每个逻辑块加括号,不靠记忆赌运算规则。
3.2 中文引号和大小写,让答案怎么都过不了
另一个很阴间的错误:字符串条件里的引号打成了中文引号。select * from world where name = 'France',如果在全角输入法状态下打引号,服务端直接报语法错误,而且错误提示往往不直观,你可能根本想不到是引号问题。排查办法很简单:把整条 SQL 删掉重打,或者用有高亮的编辑器看一眼字符串有没有被正确识别。
大小写也一样。SQL 关键字本身不区分大小写,select 和 SELECT 都能执行,但字符串的值是区分大小写的,取决于数据库排序规则。在 SQLZoo 的默认环境下,'France' 写成 'france',大概率查不到结果。数据表里明明有 France,但你就是查不出来,这时候先怀疑大小写,再怀疑引号。
补充一个经验:SQLZoo 上报错时,先把 SQL 复制到本地文本编辑器里看一眼引号、空格,再粘贴回题目重试。有时候看起来一模一样的字符串,其实混进了全角空格或换行符,肉眼看不到,删掉重打一遍就好。这类问题多遇几次,你反而会对字符敏感,以后写 SQL 会更小心。
3.3 WHERE 里用列别名,以及"别名到底能用在哪"的问题
如果你先学了 2.3 的别名语法,很容易顺手写出这种语句:
SELECT name, population AS p FROM world WHERE p > 50000000结果会报错,大致意思是 p 这一列不存在。原因就是 SQL 的执行顺序:先 FROM 确定表,再 WHERE 过滤行,最后 SELECT 投影列。WHERE 阶段别名 p 还没生成,自然没法引用。正确做法是直接写原始列名:
SELECT name, population FROM world WHERE population > 50000000再补充一个很多人搞混的点:WHERE 里不能用别名,但 ORDER BY 里可以用,这是 MySQL 的实际行为。比如 select name, population as p from world order by p desc 可以正常执行,因为 ORDER BY 发生在 SELECT 之后,此时别名已经生成。不同数据库在这个细节上不完全一致,最稳妥的做法还是统一用原始列名,能少踩很多兼容性坑。
3.4 分号、空格和字段名拼写:最不起眼也最坑
SQLZoo 判题对分号还算宽容,很多题不写分号也能过。但如果你把同样的 SQL 拿到 MySQL 命令行执行,分号就变成语句结束的标志,不写它命令行会认为句子没写完。这个差异很影响体验。我的习惯是,无论在哪,写完一条完整 SQL 都加分号,保持肌肉记忆一致,省得来回切换。
还有格式问题:SQLZoo 上完整一行写查询也行,但可读性太差。我一般按关键字换行,SELECT 一行、FROM 一行、WHERE 条件一行。判题只看逻辑结果,不看行格式,所以排版不影响正确性。但养成良好排版习惯,遇到几百行的大查询,你才知道它有多救命。
表名和列名拼写错误也会让你收获一个红色错误页。world 表里是 name,不是 names;是 population,不是 populaton。这类问题没有太多捷径,唯一建议是做题前先扫一眼页面上给出的表结构字段列表,SQLZoo 每个页面都贴了表结构,把字段名看清楚再下笔,能省掉一半报错。
4. 从 SQLZoo 到真实 MySQL:环境差异与落地验证
4.1 装一个本地 MySQL,把练习题亲手跑一遍
刷完在线题后,我强烈建议装一个本地 MySQL,把练过的题重跑一遍。原因很简单:SQLZoo 帮你屏蔽了环境问题,但真实工作中你总得连数据库、跑脚本,早一点接触客户端环境,少一些临场慌张。
本地安装其实不难。到 MySQL 官网找 community server 版本,Windows 下安装向导会引导你设置 root 密码;Linux 下可以用系统包管理器安装,装完执行 mysql_secure_installation 做基本初始化。装好后命令行执行 mysql -u root -p 能进去,就算成功了。
接着可以建一个练习库,把 SQLZoo 里那两张表简化一下建出来:
CREATE DATABASE sqlzoo_practice; USE sqlzoo_practice; CREATE TABLE world ( name VARCHAR(100), continent VARCHAR(50), area INT, population BIGINT, gdp DECIMAL(20,2) ); INSERT INTO world (name, continent, area, population, gdp) VALUES ('France', 'Europe', 640679, 67000000, 2715518.00);有了本地环境,你再跑一遍刚才那些 select 语句,会明显感受到和在线平台的区别:查询结果出来得更直接,你能看到真实客户端如何处理结果集,也可以顺手试一下不写分号、大小写混用会怎样。这种"折腾"本身就是学习。
4.2 SQLZoo 判题和 MySQL 客户端在细节上的差异
几个典型差异值得记下来。
第一是分号。SQLZoo 多数情况不强制加分号,但 MySQL 命令行不加分号就不执行,必须用分号结尾再回车。
第二是字符串引号。标准 SQL 习惯用单引号,SQLZoo 也是这个约定。MySQL 默认同时支持单引号和双引号,但在某些模式下双引号会被当成标识符引号,所以为了稳妥,统一用单引号。
第三是标识符引号。MySQL 里如果表名或字段名和保留字冲突,需要用反引号括起来,比如 selectselectfrom t 这种极端情况。SQLZoo 的题目基本不会遇到,但本地建表时要特别注意,别用 order、group 这类保留字当字段名。
第四是 LIMIT。SQLZoo 有些题目不希望用 LIMIT,它更希望你写通用 SQL;但真实 MySQL 里 LIMIT 是分页标配,不仅能用,还非常频繁。刷题时如果某个方案被判错,先看看是不是用了平台限制的写法,再去查语法本身的问题。
4.3 数据类型决定 select 出来后长什么样
刷题刷的是逻辑,真落地时,字段的数据类型会直接影响 select 看到的内容。比如 datetime 类型在 MySQL 里默认显示成 '2024-06-01 14:30:00',但如果业务只需要日期,就要在 select 里做格式化:
SELECT name, DATE_FORMAT(created_at, '%Y-%m-%d') AS created_date FROM user_info数值和 NULL 的组合也很常见。如果 select 出来的列里有 NULL,结果集里显示的是 NULL 而不是 0,做统计对比时容易看错。处理 NULL 一般用 COALESCE 或 IFNULL 转成默认值。SQLZoo 的基础题里也有一类专门练 IS NULL 和 IS NOT NULL,因为 NULL 不能直接用 = 或 != 比较。碰到这类题不要跳过,它会帮你处理真实数据库里的各种"空值"情况。
5. 刷完 SELECT 基础之后:进阶路线与面试衔接
5.1 聚合函数与 GROUP BY:从"查出来"到"算出来"
SELECT 基础练完,下一步通常是聚合。SQLZoo 的 SUM and COUNT 模块就是把基础 select 和聚合计算接起来的转折点。你会开始写这样的语句:
SELECT continent, COUNT(*) AS country_count FROM world GROUP BY continentCOUNT、SUM、AVG、MAX、MIN 这些聚合函数配合 GROUP BY,能完成"按某个维度统计"的核心需求。这里最需要留意的还是执行顺序:先分组,再聚合,再过滤。如果要对分组后的结果过滤,用 HAVING 而不是 WHERE,这一点几乎每次面试都会考。比如只显示国家数超过 20 的大洲:
SELECT continent, COUNT(*) AS country_count FROM world GROUP BY continent HAVING COUNT(*) > 20这一步完成,你就从"会查表"变成了"会算数",可以开始处理真实的数据分析需求。
5.2 子查询和 JOIN:基础再往上一层
SQLZoo 里的 SELECT in SELECT 模块讲的就是子查询:一层 select 套在另一层 select 的条件里。子查询的基础理解是,把内层 select 的结果当作外层条件的判断依据。比如:
SELECT name FROM world WHERE population > (SELECT population FROM world WHERE name = 'France')这条语句能查出所有人口比法国多的国家。内层先算出法国人口,外层拿这个值去比较。如果基础阶段没弄明白"SELECT 是怎么一层层组织起来的",到这里会非常懵。
JOIN 则是把多张表拼接起来。SQLZoo 的 JOIN 模块用 game 和 goal 表演示比赛与进球关系,很适合理解 LEFT JOIN、INNER JOIN 的区别。这两块内容排在 SELECT 基础后面是有原因的——它们都建立在"先会选列、会过滤行、会给别名"的基础上。基础没打牢,到 JOIN 就会卡住。
5.3 用面试题反向检验 SELECT 掌握程度
如果你在准备面试,搜"mysql 面试题"看到的高频题,就是很好的自测清单。面试不会只让你背语法,而是给一个场景题,比如查出每个部门工资最高的员工,查出连续登录 3 天的用户,或者查出某张表里重复的数据。这些题表面看着高级,拆到底层都离不开 SELECT 基础里的列选择、条件过滤、去重、排序和分组。
与其一上来就背高级写法,不如先把 SELECT 基础练到条件反射的程度。SQLZoo 的基础题不用全部刷完,重要的是在做题过程中形成"表结构意识"和"查询意图拆解习惯":拿到一个需求,先在脑子里过一遍要查哪张表、用什么条件、最后要看哪些列,再落笔写 SQL。这个过程练熟了,面试写题和日常开发都会顺手很多。
最后分享一点个人体会。我刷完 SQLZoo 的 SELECT 基础再回看日常工作,最大的变化不是记住了多少语法,而是对"查询条件到底在过滤什么"变得更敏感。以前写 SQL 全凭感觉,报错就去搜,现在看到一条复杂查询,会先按执行顺序在脑子里过一遍:FROM 选了哪张表,WHERE 干了什么,GROUP BY 怎么分组,SELECT 里哪些字段合法,ORDER BY 排的是什么,LIMIT 最后切了多少行。这套思考方式是在刷题时练出来的,比记住一条具体语法值钱得多。如果你也在学 MySQL,建议别贪快,把 SQLZoo 的基础 select 题认认真真刷完,最好是每一题都亲手写一遍,不要复制别人的答案。遇到报错先自己看提示找原因,实在不行再看讨论区,这种靠自己去排查、理解和修正的过程,才是练习记录里最宝贵的部分。