学SQL这件事,我见过太多人卡在同一个地方:书看了一堆,教程视频收藏了几百个,真打开编辑器面对一张表,脑子里却一片空白。“我知道这个语法”和“我知道这道题该用这个语法”之间,隔着一道巨大的鸿沟,而填平这道鸿沟的唯一方式就是实战刷题。
牛客网的SQL实战题库,是我这些年带新人用得最多的一套训练材料。它不像LeetCode那样偏算法,也不像普通教材那样只讲语法不讲场景,而是用一套从易到难的题,把SQL的核心知识点逐个打穿。这篇文章就是基于这套题库整理的一份完整通关笔记,从环境搭建、基础查询,到多表连接、聚合分组、窗口函数,再到索引优化和真实业务迁移,全程按零基础也能跟上的节奏来写。
如果你是那种“学过SQL但写不出”“准备校招笔试但心里没底”“工作中要用SQL但只会复制粘贴”的人,这篇文章应该能帮你少走很多弯路。
1. 牛客网SQL实战这个题库,到底在练什么
1.1 题库的难度分层:从“能跑通”到“能写对”
打开牛客网的SQL实战板块,你会看到题目不是随机堆在那里的,而是按难度递进排列的。整体可以分成四个层次:
- 第一层是入门基础题,基本就是单表查询。给你一张员工表,让你查最晚入职的员工、查薪水排名倒数第三的员工,涉及的知识点只有SELECT、WHERE、ORDER BY、LIMIT这几个。这个阶段的目标很单纯——把语法跑通。
- 第二层是多表连接题。两张表、三张表,让你查员工和他的部门、查经理和下属、查没有分配部门的员工。这里开始涉及JOIN系列,也是很多零基础同学第一次“卡壳”的地方。
- 第三层是聚合与分组题。比如统计每个部门的平均薪水、查找薪水涨幅超过15次的员工、找出各个部门工资最高的员工。核心是GROUP BY、HAVING和聚合函数的组合。
- 第四层是进阶题,涉及子查询、临时表、窗口函数和各类函数处理。比如查找第N高的薪水、计算累计薪水、处理日期和字符串的题目。这个阶段最接近互联网公司笔试的真实难度。
1.2 为什么它比单纯看书更适合零基础
我见过不少新人,MySQL的语法书翻了三遍,一关页面就失忆。原因很简单——看书是线性输入,而写SQL是网状检索。你在实际写题的时候,需要同时调动“这张表有哪些字段”“这个条件怎么表达”“这个结果要不要去重”“排序规则是什么”等多个维度的信息,这种综合能力只有靠做题才能练出来。
牛客网这套题库还有一个好处:每道题都有在线运行环境,你写出SQL直接跑,结果对就对,不对就是不对,没有模糊地带。这种即时反馈对新手建立信心特别重要。另外,题解区和讨论区里有大量前人的思路总结,你写完自己的答案之后再去对照,能明显感觉到自己的写法哪里笨了、哪里漏了。
还有一个容易被忽略的点:牛客网SQL题目的表结构设计,非常贴近国内互联网公司的真实业务。员工表、部门表、薪水表、职位表,这些字段命名和数据关系,跟你在公司里遇到的表几乎一个套路。刷完这套题,再去面国内大厂的数据分析岗、后端开发岗,遇到SQL笔试题心里会踏实很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备别再卡壳:数据库选型与安装避坑
2.1 本地该装哪个数据库:MySQL优先的理由
刷牛客网的SQL题,理论上不需要本地环境,网页上直接就能跑。但我强烈建议你本地装一个数据库,原因后面会讲。选哪个数据库?我的建议是MySQL,理由有三个:
- 牛客网SQL实战的判定逻辑兼容MySQL语法,你本地练的写法,和提交到网页上的结果能保持一致。
- MySQL是互联网公司用得最广的开源数据库,你以后工作大概率也要碰它,现在装好不算白装。
- 网上关于MySQL的教程、报错解决方案多到数不清,零基础遇到问题基本都能搜到答案。
那SQL Server呢?如果你后续工作明确用微软系技术栈,装SQL Server 2019或2022也可以,但它的语法和MySQL有一些细微差别,比如LIMIT要改成TOP或者OFFSET FETCH,字符串拼接用+而不是CONCAT。对零基础来说,专注一个数据库先把SQL练扎实是最重要的,别分散注意力。
2.2 从下载到建库建表的完整流程
我用Windows环境举例,MySQL 8.x的安装流程大概是这样:
- 去MySQL官网下载MySQL Community Server的安装包,选MSI Installer格式。
- 安装时选择Developer Default,一路下一步。
- 到了设置root密码那一步,建议记在备忘录里。密码最好别搞太复杂,本地练习用,root/123456这种级别完全够。
- 安装完成后,在系统服务里确认MySQL服务处于“正在运行”状态。
命令行验证非常关键,很多人装完根本不确定装好没有:
bash复制mysql -u root -p
如果提示输入密码后进入mysql>提示符,说明安装成功了。然后执行一段最简单的SQL验证功能:
sql复制SELECT 1;
能看到返回结果1,环境就算通了。
接下来我建议装一个可视化工具,整天在黑框里敲SQL对新手太劝退了。开源工具里我推荐DBeaver,跨平台、免费、功能比很多收费工具都全,而且可以直接连MySQL、PostgreSQL、SQL Server等多种数据库。HeidiSQL也可以,更轻量,但只支持Windows。安装完DBeaver之后,新建一个MySQL连接,填主机localhost、端口3306、用户名root、密码,点击测试连接,通了就完事。
2.3 常见安装与连接报错的排查思路
这一步拦住了不少人,我把高频问题列一下,你遇到了可以直接对号入座:
- 服务无法启动,报错没提到具体原因。多半是之前装过MySQL没卸干净。打开services.msc看服务状态,或者把原来的MySQL目录删干净再重装。
- 端口3306被占用。用
netstat -ano | findstr 3306查一下是哪个进程占了端口,通常是其他数据库或旧版MySQL。改端口是最快的办法,但注意后续连接也要改。 - 连接时提示
Access denied for user 'root'@'localhost'。密码不对是最常见的原因,想想有没有记错密码。如果确实忘了,网上搜“MySQL root密码重置”有完整的处理流程,核心思路是跳过权限表启动再改密码。
有时候报错信息一大串,你要学会看最关键的那一段。比如[28000] [microsoft][odbc driver 17 for sql server][sql server]用户 'sa' 登录失败这类报错,本质就是账号或密码不对,跟数据库本身没关系。记住一个原则:先确认账号密码,再看端口和服务状态,90%的连不上问题都能解决。
本地环境搞定之后,建议你从牛客网的题目里复制一张表的建表语句,自己建几个表,插入几条数据,随便跑几条查询找找感觉。这一步的意义在于,网页上跑题是“别人给你准备好数据”,本地建表是“你自己设计数据”,后者对理解表结构非常有帮助。等刷到后面的连接题,你手里有一张自己建的员工表、部门表、薪水表,试错成本会低很多。
3. 基础查询的稳扎稳打:SELECT背后的执行逻辑
3.1 别急着背语法,先理解数据是怎么流动的
很多零基础学SQL,上来就背SELECT、FROM、WHERE的顺序,结果一写就错。原因在于,SQL的书写顺序和它的执行顺序根本不一样。拿一条最常见的查询来说:
sql复制SELECT dept_no, COUNT(*) AS cnt
FROM dept_emp
WHERE dept_no != 'd001'
GROUP BY dept_no
HAVING COUNT(*) > 3
ORDER BY cnt DESC
LIMIT 5;
书写顺序是SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT,但真正的执行顺序是:
- FROM:确定要查哪张表,如果有JOIN就在这里做连接。
- WHERE:对每一行进行过滤,把不满足条件的行扔掉。
- GROUP BY:把剩下的行按指定字段分组。
- HAVING:对分组结果进行过滤,扔掉不满足条件的分组。
- SELECT:计算要返回的列,包括聚合函数、表达式、去重等操作。
- ORDER BY:对最终结果排序。
- LIMIT:截取指定行数。
理解这个顺序最大的好处是,你能一眼看出一句SQL哪里错了。比如WHERE里面不能用聚合函数,因为WHERE执行的时候还没分组呢,你拿COUNT(*)去过滤当然报错。又比如SELECT里给字段取的别名,不能在WHERE里用,因为WHERE执行在SELECT之前。
3.2 WHERE、ORDER BY、LIMIT的常见组合坑
WHERE里的坑,印象最深的是AND和OR的优先级问题。SQL里AND的优先级高于OR,所以WHERE a = 1 OR a = 2 AND b = 3实际执行的逻辑是a = 1 OR (a = 2 AND b = 3),而不是你直觉上以为的(a = 1 OR a = 2) AND b = 3。这属于经典面试题,也确实在工作中坑过人。我的建议是:只要WHERE条件里同时出现AND和OR,一律加括号,别省那两下键盘。
BETWEEN AND也是高频考点。它在MySQL里是闭区间,BETWEEN 100 AND 200包含100和200本身。跟日期一起用的时候要格外注意,如果你只想查某一天的数据,BETWEEN '2024-01-01' AND '2024-01-01'是查不到多少数据的,因为日期筛选本质是比较字符串或时间戳。正确做法是date >= '2024-01-01' AND date < '2024-01-02',或者用DATE()函数把时间部分去掉再比较。这个坑在牛客网的日期类题目里特别常见,我自己一开始也在上面栽过。
LIMIT的用法也分两种写法。MySQL和PostgreSQL支持LIMIT offset, count,比如LIMIT 2, 1表示跳过前2行取1行,也就是第3行。还有一种写法是LIMIT count OFFSET offset,效果一样。牛客网有一道很经典的题——“查找入职员工时间排名倒数第三的员工”,很多人第一反应就是ORDER BY hire_date DESC LIMIT 1 OFFSET 2,这个思路没问题,但要注意如果存在并列排名,LIMIT 2,1只会取其中一条,碰到这种题你得先想清楚题目要的是“第3条记录”还是“排名第3的所有记录”,这是两种不同查法。
3.3 去重与空值:两个必考细节
去重是SQL笔试里绕不开的点。DISTINCT用起来简单,但有个坑很多人不知道:SELECT DISTINCT a, b FROM table去重的是a和b的组合,不是单独的a。如果你想“按a去重但返回b”,SQL标准写法是办不到的,得用窗口函数或者GROUP BY来曲线救国。所以做题前先看清题目要你去重的粒度,是一个字段还是多个字段的组合。
空值的处理更是重灾区。我见过很多新手写WHERE column = NULL,结果查出来永远是0行。原因很简单,NULL不是值,它表示“未知”,所以判断空值只能用IS NULL或者IS NOT NULL,用=、!=去比较NULL结果都是UNKNOWN,会被WHERE当假值过滤掉。除了NULL判断本身,还要注意空字符串和NULL是两回事。''是一个真实的值,NULL是什么都没有,有些业务表用空字符串表示“没填”,有些用NULL,实际写SQL之前先摸清表的规则。
关于空值的处理,还需要掌握COALESCE和IFNULL。COALESCE是标准SQL,可以传多个参数,返回第一个非NULL的值;IFNULL是MySQL特有的,只接受两个参数,第一个非NULL就返回第一个,否则返回第二个。这俩在牛客网的题目里经常出现,比如查员工薪水时,如果某员工没有薪水记录要显示成0,就可以用IFNULL包裹。
4. 多表连接:从“一张表”到“一堆表”的思维转变
4.1 JOIN的本质是“笛卡尔积加过滤条件”
多表连接是零基础到进阶的第一道大坎。很多人记不住各种JOIN的区别,本质是没理解JOIN到底在干什么。我打个比方:两张表连接,就像两副牌叠在一起做排列组合——第一张表的每一行,去匹配第二张表的每一行,形成所有可能的配对,这个全集就是笛卡尔积。
JOIN做的事情,就是在这个全集上加上过滤条件,只留下满足条件的配对。INNER JOIN取交集,LEFT JOIN取左边全部加右边匹配上的,RIGHT JOIN反过来,FULL OUTER JOIN取并集。理解这个底层的“配对视”之后,各种JOIN的区别就一目了然了。
牛客网的多表题目,表关系就那么几类:员工表和部门表是一对多,员工表和薪水表是一对多,部门表和部门经理表是一对一。做题的第一步永远是先搞清楚两张表之间的关联字段是什么,用哪个字段做连接键。这一步搞错了,后面一切白搭。
4.2 LEFT JOIN的行数为什么突然变多了
我印象最深的一个坑是:明明用的是LEFT JOIN,按理说左边表的行数应该保持不变,但查出来的结果行数比左边表还多。很多新手在这里卡了很久,以为是JOIN写错了。
其实问题出在一对多关系上。如果左边表的一行,在右边表里能匹配到多行,那么LEFT JOIN的结果里,这一行会被复制成多行。举例来说,员工表里有一条小王,薪水表里有小王的三条涨薪记录,LEFT JOIN之后小王就会出现三行。所以结果行数不等于左表行数,而是等于“左表每一行匹配右表的行数之和”。
碰到这种情况,先不要怀疑语法,去思考数据关系。想要结果里每个员工只出现一行,通常需要先对右表做去重或聚合,再去JOIN,或者直接用窗口函数取第一条记录。这种题在牛客网里对应“查找所有员工当前薪水情况”之类的题目,表面是连接,实际考的是你对数据粒度的理解。
4.3 ON和WHERE的过滤时机:这俩真不一样
ON和WHERE都能写条件,但执行时机完全不同。ON是JOIN过程中决定两行匹不匹配的条件,WHERE是在JOIN完成之后对结果的过滤条件。在INNER JOIN里,二者效果差不多;在LEFT JOIN里,差别就大了。
举个例子:SELECT * FROM emp LEFT JOIN dept ON emp.dept_id = dept.id AND dept.name = '研发部',这个条件写在ON里,表示连接的时候只匹配研发部的部门,没匹配上的员工仍然会出现在结果里,只是部门字段是NULL。但如果把dept.name = '研发部'移到WHERE里,那LEFT JOIN已经把所有员工和部门连完了,WHERE再把没有研发部的员工行过滤掉,效果就变成“只有研发部员工的查询结果”了。
牛客网有好几道题考这个点。比如查“所有员工的last_name和first_name以及对应部门编号”,其中有些员工还没分配部门,这种题必须用LEFT JOIN,而且部门条件只能写在ON里,不能写在WHERE里,否则那些没分配部门的员工就被过滤掉了。我在面试别人的时候也喜欢问这个问题,能答清楚的人,说明真的理解了JOIN的运行机制。
5. 聚合分组和窗口函数:进阶题的核心分水岭
5.1 GROUP BY + HAVING:分组后再筛选的正确姿势
聚合查询是牛客网SQL题里分量最重的一块。COUNT、SUM、AVG、MAX、MIN这几个聚合函数本身不难,难的是跟GROUP BY组合之后的操作逻辑。
GROUP BY的机制是:把数据按指定字段分组,然后对每一组分别做聚合计算。这个过程中有一个铁律——SELECT里出现的非聚合列,必须出现在GROUP BY里。比如SELECT dept_no, AVG(salary) FROM salaries GROUP BY dept_no是合法的,但SELECT dept_no, emp_no, AVG(salary) FROM salaries GROUP BY dept_no就不合法,因为emp_no没有被分组,数据库不知道这一组里该显示哪个emp_no。MySQL有个ONLY_FULL_GROUP_BY模式,默认是开启的,这种写法会直接报错。
HAVING和WHERE的区别前面讲执行顺序的时候提过一嘴,这里再展开:WHERE是在分组之前过滤行,HAVING是在分组之后过滤组。牛客网有一道“查找薪水涨幅超过15次的员工号emp_no以及其对应的涨幅次数t”的题,标准的解法就是:
sql复制SELECT emp_no, COUNT(*) AS t
FROM salaries
GROUP BY emp_no
HAVING t > 15;
这里只能用HAVING,因为COUNT(*)是分组之后才计算出来的,WHERE压根拿不到这个值。分组聚合的题目做多了你会发现一个套路:凡是题目里出现“每个”“各个”“超过”“前N”这种词,大概率要用GROUP BY加HAVING。
5.2 窗口函数:ROW_NUMBER、RANK、DENSE_RANK到底怎么选
窗口函数是牛客网题库里从基础到进阶的分水岭,也是很多人觉得“难”的地方。其实窗口函数的语法并不复杂,就是函数() OVER (PARTITION BY 分组字段 ORDER BY 排序字段)。它跟GROUP BY最大的区别是:GROUP BY会把多行合并成一行,而窗口函数不改变行数,每一行都能保留,只是多了一个计算出来的列。
最常用的三个排名函数,网上讲得很多,但很多人还是分不清。我直接用例子讲:
假设一张员工薪水表,有三个员工的薪水分别是100、90、90、80,按薪水从高到低排名。
- ROW_NUMBER():只关心行号,不管值是否相同,结果是1、2、3、4。
- RANK():相同值并列排名,但下一个排名会跳号,结果是1、2、2、4。
- DENSE_RANK():相同值并列排名,但下一个排名不跳号,结果是1、2、2、3。
很多零基础第一次看这个表格,觉得都差不多,一上题就傻眼。做题之前一定先看题目要求:“排名”到底允不允许并列?“第2名”后面接的是“第3名”还是“第4名”?牛客网里有一道经典的“查找所有员工的薪水排名”,就会同时用到这三种函数的区别,你提交一次就知道自己选没选对。
5.3 经典题型拆解:“第N高”“分组TopN”“连续出现”
窗口函数解决了SQL里一大类“取前N条”的问题。我把最常见的三类题型的解法模板放在这里,直接套用就行。
第一类是“第N高的薪水”。不管N是1还是3,核心思路都是先用DENSE_RANK或者ROW_NUMBER排名,再取排名等于N的那行:
sql复制SELECT DISTINCT salary
FROM (
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rk
FROM employees
) t
WHERE rk = 1;
注意这里用DENSE_RANK而不是ROW_NUMBER,因为薪水相同的员工应该算同一个名次。牛客网有一道题的坑就在这——直接ORDER BY LIMIT的话,遇到并列薪水就会漏数据。
第二类是“每个部门工资最高的员工”,也就是分组取TopN。解法是用PARTITION BY按部门分组,然后按薪水排序取组内第一:
sql复制SELECT dept_no, emp_no, salary
FROM (
SELECT dept_no, emp_no, salary,
ROW_NUMBER() OVER (PARTITION BY dept_no ORDER BY salary DESC) AS rn
FROM salaries
) t
WHERE rn = 1;
这个模式在牛客网题目里反复出现,属于必须烂熟于心的模板。
第三类是“找出连续出现N次”的题目。这类题通常涉及自连接或者窗口函数的LAG/LEAD。比如查找连续3个月都有涨薪记录的员工,可以先按员工分组、按月排序,然后用LAG取上一个月的记录,判断日期是否连续。这类题在牛客网里属于进阶题,刷到后面自然会遇到,先了解思路就行。
窗口函数还有一个特别实用的场景,就是跟自增序号组合去重。前面提到DISTINCT解决不了“按A去重但返回B”的问题,用ROW_NUMBER() OVER (PARTITION BY A ORDER BY 某字段)之后再取rn=1,就是标准的解法。这个技巧在真实工作中,比DISTINCT好用十倍。
6. 从刷题到真实的业务,优化和安全躲不开
6.1 索引为什么能提速:从全表扫描到“走索引”
牛客网的题目数据量都不大,所以你会发现,不管怎么写,查询都能秒回。但真实业务里一张表可能几千万行,SQL写法不同,性能差距可能是几毫秒和几分钟的区别。刷完基础题之后,不管你是要走数据分析还是后端开发路线,索引和SQL优化这关迟早要过。
索引最粗浅的理解就是书的目录。没有索引的时候,数据库要查某一行,只能从头到尾一页页翻,这叫全表扫描。有了索引,数据库直接根据索引找到数据的位置,速度自然快很多。MySQL的InnoDB引擎默认用B+树结构来组织索引,非常适合范围查询和排序。
做优化的时候,第一步永远是看执行计划。MySQL里在SQL前面加EXPLAIN,就能看到这条查询有没有走索引、扫描了多少行数据:
sql复制EXPLAIN SELECT * FROM employees WHERE emp_no = 10001;
重点关注type和rows两列。type列的常见值从好到差大致是:system > const > eq_ref > ref > range > index > ALL。ALL就是全表扫描,看到这个就要警惕了。rows列是预估扫描的行数,数字越大越需要优化。
6.2 慢SQL的常见姿势和排查思路
我见过太多慢SQL,其实都是同样的几个毛病。第一条是SELECT *。你以为没什么,但在大表上,SELECT *会把这行所有字段都捞出来,网络传输成本和内存开销全部放大。正确做法是只查你需要的字段。第二个是WHERE条件里的字段用了函数包裹,比如WHERE YEAR(hire_date) = 2024,这时候索引就废了,因为数据库要对每一行算完函数之后才能比较。解决办法是改成WHERE hire_date >= '2024-01-01' AND hire_date < '2025-01-01'。第三个是LIKE前置通配符,LIKE '%abc'走不了索引,LIKE 'abc%'能走,这个区别在实际优化中经常用到。
排查慢SQL的完整链路大致是这样:先通过慢查询日志或监控平台抓到那条慢SQL,然后EXPLAIN看执行计划,确认是全表扫描还是索引没走对,再针对性地改写SQL或者加索引。加索引不是乱加,一张表索引太多会影响写入性能,要优先给WHERE条件、JOIN的关联字段、ORDER BY的排序字段加索引。
这里必须提一嘴SQL注入的问题。网上热搜里经常出现“sql注入万能密码绕过”“sql注入绕过登录”这类词,它们的本质都是把用户输入直接拼进了SQL字符串,导致数据库把输入当成代码执行。比如登录功能里,如果代码直接写:
python复制sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"
用户输入用户名admin' --,后面的密码验证就被注释掉了,直接绕过登录。真实业务里的防护手段是参数化查询,让数据库把用户输入当纯数据处理,而不是SQL代码。这个知识点在牛客网刷题里不会直接考,但如果你要进入开发岗位,面试官大概率会问。
6.3 刷题和真实业务的差距,差的不是一个数据量
刷完牛客网SQL实战的题,你可能会觉得自己已经会写SQL了,但真进了公司,还是会发现自己缺了一块——业务理解。
牛客网题目里的表结构,字段含义是明确的,表关系是设计好的,你只需要按题目要求写出正确的SQL。但真实业务里,一张表可能有几十个字段,其中一半是你不知道含义的;一个“订单”可能分散在订单主表、订单明细表、退款表、赠品表里,你得自己搞清楚数据口径,才能写出对的SQL。这就是为什么很多公司在面试时会问“如果让你统计某段时间的GMV,你会怎么查”——考点不只是SQL语法,还有你对数据粒度和统计口径的理解。
所以刷题的时候,建议你多留一个心眼:每做完一道题,想一想它在真实业务里对应什么场景。查“每个部门工资最高的员工”,在业务里就是“找出每个区域的销售冠军”;查“累计薪水”,在业务里就是“计算余额或库存的流水账”。带着这个视角去刷题,你会觉得SQL从“应付考试”变成了“解决问题的能力”。
7. 我的刷题路线图:三轮刷透SQL实战
7.1 我建议的学习顺序和时间分配
如果你完全零基础,我推荐的路径是:先花两三天把基础语法过一遍,知道SELECT、WHERE、ORDER BY、GROUP BY、JOIN这些关键词是干什么用的,然后直接进牛客网开刷。不要等“准备好了”再刷,SQL不是看会的,是写会的。
第一轮:按顺序从第1题刷到最后一题,目标是每道题都能自己独立写出来,实在卡住可以看题解,但看完要自己重新写一遍。这个阶段大概需要两到三周,每天1到2个小时。别贪快,刷题的感觉比刷题的数量重要。
第二轮:按知识点分类重刷。把已经做过的题按“基础查询”“多表连接”“聚合分组”“窗口函数”“日期函数”等主题归类,每个主题一天集中刷完。你会发现很多题看起来不一样,内核是同一个套路。这一轮重点是把套路内化成肌肉记忆。
第三轮:限时模拟。给自己规定时间,比如30分钟做5道题,模拟笔试的节奏。牛客网本身有模拟笔试功能,可以多用用。这个阶段的目标是速度和准确率,因为真实笔试的时候,SQL题通常只给你20到30分钟。
7.2 刷题时最容易犯的“自我欺骗”
我带过不少人刷题,发现一个普遍现象:看题解时觉得自己懂了,合上答案自己写就卡壳。这是因为“看答案”和“写答案”激活的是完全不同的认知过程。看答案时你是被动接收信息,大脑会给你一种“我会了”的错觉;自己写的时候,你要主动检索语法和思路,这个过程的难度比前者高好几倍。
所以我的建议很直白:一道题如果卡了20分钟还没思路,可以看题解,但看完之后必须把答案关掉,自己从头到尾写一遍,直到能解释清楚每一步为什么要这么写。如果你写完之后隔天还能独立写出来,那这道题才算真正属于你了。
另一个容易犯的问题是不做总结。刷了50道题,每道题用完就扔,跟没刷没什么区别。我自己的习惯是准备一个笔记,按知识点把每道题的解法模板记下来。比如“第N高”类题目统一记一个窗口函数模板,“分组TopN”记一个PARTITION BY模板,“连续出现”记一个LAG/LEAD模板。下次再遇到类似题目,直接翻模板,效率比重新花20分钟思考高得多。
7.3 最后分享一个我的压箱底技巧:把题解变成自己的知识
刷题不记笔记,等于白刷。但记笔记也有讲究,不是把别人的题解抄一遍,而是把每道题的精髓压缩成自己能看懂的一句话。比如我在窗口函数那儿记的一句话是:“排名函数永远别用ROW_NUMBER替代RANK——除非题目明确说忽略并列。”这就是典型的“自己踩过坑才知道”的笔记,比抄十行代码都管用。
还有一个小技巧:给每道题打两个标签,一个是知识点标签(如“LEFT JOIN”),一个是坑点标签(如“ON和WHERE陷阱”)。这样做完几十道题之后,你会发现那些坑点高度集中在几个地方——空值处理、去重粒度、连接条件位置、并列排名。下次再遇到新题,你大概率能预判到它准备在哪里埋坑,这种“预判感”对笔试拿高分特别重要。
我在实际带新人的过程中发现,能把牛客网SQL实战这套题库做完两轮的人,SQL基础都已经很扎实了。但真正拉开差距的,是刷完之后有没有形成自己的知识框架。所以别急着追求刷题数量,把每个知识点吃透,一步一个脚印往前推,SQL这条路不会辜负你。
