SQLZOO中文版习题答案精讲:从SELECT入门到窗口函数进阶
2026/9/13 6:37:48 网站建设 项目流程

SQLZOO这个名字,只要是认真学过SQL的人应该都不陌生。它是国外一个免费的SQL在线练习平台,按知识点拆成十几个模块,从最简单的SELECT语句一路做到JOIN、聚合、子查询、窗口函数,每道题都是直接在一个真实数据库上跑SQL验证结果。我当年就是靠这份中文版习题一点点把SQL底子打扎实的,后来带新人学SQL,我也都是直接甩这个链接,让他们先把这套题刷完再说话。

这篇东西不是光把答案贴一遍就完事,而是按模块拆解题目的设计意图、每个知识点的核心逻辑,以及做题时最容易踩的坑。无论你是刚看完SQL语法想找题练手的学生,还是工作中写SQL总是差点意思的开发、数据分析师,又或者是面试前想系统性过一遍SQL重点的人,这套题库和这份解析都足够你认真啃上一段时间。

1. SQLZOO到底练什么:整套习题的模块拆解

1.1 从SELECT basics到窗口函数:一条完整的SQL学习路线

SQLZOO中文版把SQL学习分成了十几个递进模块,每个模块聚焦一组核心语法,整体路线很清晰:

  • SELECT basics:最基础的列筛选,认识SELECT和WHERE
  • SELECT from WORLD:在真实国家数据表上练习条件过滤、BETWEEN、IN、LIKE
  • SELECT from Nobel:用诺贝尔奖得主数据练习更复杂的WHERE组合
  • SELECT within SELECT:子查询,即一个查询嵌套在另一个查询里
  • SUM and COUNT:聚合函数和GROUP BY,配套HAVING过滤
  • JOIN:两张表如何通过关联字段合并查询
  • More JOIN:多表连接的高级场景,比如演员和电影表
  • Using Null:COALESCE、IS NULL、LEFT JOIN等空值处理
  • Self join:自连接,模拟同一张表内部的结构化关联
  • 窗口函数(Window functions):ROW_NUMBER、RANK、LAG等分析函数

这条路线设计的精妙之处在于:它不是一股脑把所有语法堆给你,而是刻意制造了一个理解坡度。你刚刚觉得“SELECT好像也不过如此”,下一模块立刻用嵌套子查询打脸;你刚觉得JOIN已经摸透了,Using Null和Self join又告诉你“连接还能这么玩”。

1.2 为什么刷题比看教程有效

很多人学SQL有个误区,就是拿一本《SQL必知必会》从头翻到尾,看的时候觉得全懂了,一落到真实数据上就懵。SQLZOO逼你直接在数据库上写SQL、跑结果,错了就报错,对了才放行。这种“反馈闭环”恰恰是学习SQL最有效的方式——你写下的每一条语句,立刻能验证它对不对,而不是停留在理论上。

而且它用的不是那种精心构造的教学数据,而是真实世界的数据:国家人口、诺贝尔奖得主、电影演员表。这些数据有残缺、有NULL、有重复,恰好还原了实际工作中你面对的真实数据状态。

2. 核心模块习题答案精讲

2.1 SELECT basics:基础中的基础

这个模块是热身用的,只有十几道题,目标是让你熟悉最基本的SELECT语句结构。它用的是一张很小的世界表,包含name(国家名)、continent(大洲)、population(人口)几个字段。

几道代表性的题目和解法:

-- 展示法国的人口 SELECT population FROM world WHERE name = 'France'; -- 展示瑞典和挪威的人口 SELECT name, population FROM world WHERE name IN ('Sweden', 'Norway'); -- 找出人口在20万到25万之间的国家 SELECT name, population FROM world WHERE population BETWEEN 200000 AND 250000;

这个模块虽然简单,但有一个细节值得注意:BETWEEN是闭区间,也就是包含边界的。很多人写BETWEEN的时候容易忽略这一点,导致边界数据被重复计算。实际工作中如果对边界值敏感,建议直接用>=<=替代,避免歧义。

2.2 SELECT from WORLD:WHERE条件的组合艺术

这个模块的难度开始爬升,核心是组合WHERE条件。题目要求你从一个包含全世界国家数据的表里,按各种条件筛出目标国家。比较经典的几道:

-- 人口超过2亿的国家名称 SELECT name FROM world WHERE population > 200000000; -- 人口在10亿到20亿之间的国家名称和人口 SELECT name, population FROM world WHERE population BETWEEN 1000000000 AND 2000000000; -- 名称以AL开头的国家和人口 SELECT name, population FROM world WHERE name LIKE 'AL%';

这个模块里最值得深挖的是LIKE。SQL里的LIKE支持%_两种通配符:%匹配任意长度的字符串(包括0个字符),_匹配单个字符。实际工作中做模糊搜索时,LIKE的性能在大数据量下非常差,因为无法走索引,所以生产环境里一般用全文检索或者ES之类的方案。但在练习环境里,它是掌握字符串匹配逻辑的最好入口。

还有一道题我印象很深,是要求找出人口在25万以上且面积小于30万平方公里的国家:

SELECT name, population, area FROM world WHERE population > 250000 OR area < 300000;

这道题考的是OR条件。OR看起来简单,但它和UNION有个性能差异:OR条件很难走索引合并,而UNION可以分别走索引再合并结果。数据量小的时候感觉不到差别,数据量大了以后,同样的语义用UNION写往往更快。具体对比后面讲慢SQL优化的时候细说。

2.3 SELECT from Nobel:复杂条件的逻辑组合

诺贝尔奖得主表有yr(年份)、subject(学科)、winner(获奖者)三个字段。这个模块的题目很多,它真正考验的是你对多个条件的组合能力。

典型题目和解法:

-- 1962年物理奖得主 SELECT winner FROM nobel WHERE yr = 1962 AND subject = 'physics'; -- 1980年之后每届和平奖得主 SELECT winner FROM nobel WHERE yr >= 1980 AND subject = 'peace'; -- 找出所有获奖者中包含"John"的人 SELECT winner FROM nobel WHERE winner LIKE 'John%';

我特别想提一道题:找出物理学奖和化学奖得主,但要排除1964年的化学奖得主、排除1980年之前的物理奖得主:

SELECT * FROM nobel WHERE (subject = 'physics' AND yr >= 1980) OR (subject = 'chemistry' AND yr <= 1964);

这道题的核心是理解AND和OR的优先级:AND比OR高。所以上面这个写法里,括号不是可选的,而是必须的。如果把括号去掉,SQL引擎会先执行两个AND,再执行OR,结果就完全变了。很多人写复杂条件的时候不习惯加括号,全凭缩进表达逻辑,这是迟早要踩坑的,因为对SQL解析器来说,缩进什么都不是。

2.4 SELECT within SELECT:子查询的进阶用法

这个模块是整套题目的第一个分水岭,很多人就是在这里开始觉得“SQL好像没那么简单”。子查询的本质就是:先查出一个结果集,再把这个结果集当作外层查询的输入。

经典题目:

-- 找出人口超过俄罗斯的国家 SELECT name FROM world WHERE population > (SELECT population FROM world WHERE name = 'Russia'); -- 找出GDP比欧洲任何一个国家都高的国家 SELECT name FROM world WHERE gdp > (SELECT MAX(gdp) FROM world WHERE continent = 'Europe'); -- 找出与阿根廷同属一个洲的国家名和所属洲 SELECT name, continent FROM world WHERE continent = (SELECT continent FROM world WHERE name = 'Argentina');

第三道题特别有意思,它要求你先查阿根廷所属的洲,再拿这个结果去匹配外部表。子查询返回单个值的时候,就叫“标量子查询”,可以用=直接比较;如果子查询返回多个值,就要用IN或者ANY/ALL。这个区别是子查询最常见的错误来源:

-- 错误写法:子查询返回了多行,却用"="比较 SELECT name FROM world WHERE continent = (SELECT continent FROM world WHERE name IN ('Argentina', 'Brazil'));

这时候应该用IN:

SELECT name FROM world WHERE continent IN (SELECT continent FROM world WHERE name IN ('Argentina', 'Brazil'));

2.5 SUM and COUNT:聚合函数的入门门槛

这个模块引入了GROUP BY和HAVING,是SQL学习里一个真正的坎。很多人用聚合函数没问题,但一遇到GROUP BY和HAVING就晕。核心就一句话:GROUP BY把多行压缩成一组,SELECT只能出现分组字段或聚合函数

经典题目:

-- 统计每个大洲的国家数量 SELECT continent, COUNT(name) FROM world GROUP BY continent; -- 统计人口不少于1000万的大洲 SELECT continent FROM world GROUP BY continent HAVING SUM(population) >= 10000000;

第二道题里面,HAVINGWHERE的区别就体现出来了:WHERE在分组之前过滤行,HAVING在分组之后过滤组。举个例子,如果先按大洲分组再算总人口,但你想排除某个特定国家再分组,那就得用WHERE:

SELECT continent, SUM(population) FROM world WHERE name <> 'China' GROUP BY continent;

这个模块里还经常用到COUNT(DISTINCT column),作用是去重计数。实际工作中这个用法也很常见,比如统计“活跃用户数”,一个用户可能有多条记录,用COUNT(DISTINCT user_id)才能得到真正的用户数。

2.6 JOIN与More JOIN:多表连接的思维转换

JOIN是SQLZOO里篇幅最多的模块之一,因为它确实是实际工作中用得最多的能力。SQLZOO有专门的演员表和电影表,More JOIN就是在这些表上练习INNER JOIN、LEFT JOIN等。

典型解法:

-- 找出每部电影的上映年份和导演 SELECT movie.title, movie.yr, movie.director FROM movie; -- 找出所有的演员和出演的电影名 SELECT actor.name, movie.title FROM actor JOIN casting ON actor.id = casting.actorid JOIN movie ON casting.movieid = movie.id;

这个模块里最有价值的一道题是:列出每部电影的演员数量。它需要先用GROUP BY按电影分组,再统计每个电影的演员数:

SELECT movie.title, COUNT(actorid) FROM movie JOIN casting ON movie.id = casting.movieid GROUP BY movie.id;

这里有个SQL细节:GROUP BY movie.id用的是主键,而不是movie.titlemovie.name。为什么?因为不同电影可能有同名但不同年份的情况,按主键分组才是最可靠的分组依据。这是实际开发中被反复强调的原则:GROUP BY要用唯一标识,别用容易重复的字段。

2.7 Using Null:处理空值的那点事

这个模块专门教你如何处理NULL。NULL是SQL里最容易出幺蛾子的地方,因为NULL不是“空字符串”,也不是“0”,而是“未知值”。任何NULL参与的算术运算结果都是NULL,任何NULL参与的比较结果都是UNKNOWN,而WHERE只接受TRUE,不接受UNKNOWN。

典型题目:

-- 找出没有出演任何电影的演员 SELECT name FROM actor WHERE id NOT IN (SELECT actorid FROM casting); -- 危险版本:如果casting.actorid里有NULL,上面这条可能返回空结果

这个坑特别隐蔽:如果子查询返回的结果集中包含NULL,NOT IN会返回空集,而不是你预期中的“不在里面”的记录。原因是NULL参与NOT IN比较时,结果既不是TRUE也不是FALSE,而是UNKNOWN,所以整条记录被过滤掉了。正确的做法是用NOT EXISTS

SELECT name FROM actor WHERE NOT EXISTS (SELECT 1 FROM casting WHERE casting.actorid = actor.id);

SQLZOO里还有配套的COALESCE函数练习,它可以把NULL替换成默认值:

SELECT name, COALESCE(party, 'No party') FROM teacher;

实际工作中处理NULL,第一原则就是:别想当然地认为NULL会怎样参与运算,先想清楚NULL对结果的影响,再决定是用COALESCE、IS NULL还是NOT EXISTS。

2.8 Self join:一张表的自我关联

Self join,中文叫自连接,就是用一张表和自己做JOIN。这个模块最经典的例子是“找出伦敦巴士线路中,所有可以换乘的车站对”。

典型解法思路:

-- 找出所有“从A站到B站,再从B站到C站”的换乘关系 SELECT a.company, a.num FROM route a JOIN route b ON (a.company = b.company AND a.num = b.num) JOIN stops s ON a.stop = s.id WHERE s.name = '某站名';

这里route表既是起点表,又是终点表。自连接的核心理解点在于:JOIN时不限定必须两个不同的表,同一个表只要起了不同的别名,就可以把它当作两个不同的集合来关联。我见过不少人在工作里碰到“同一张表要关联两次”的时候绕不过弯来,其实就是别怕起别名,把它想象成两张结构相同的表就行。

2.9 窗口函数:现代SQL的进阶必修课

窗口函数是SQLZOO后来加入的模块,也是现在面试的高频考点。窗口函数和GROUP BY的核心区别在于:GROUP BY会把多行合并成一行,窗口函数不会——它保留每一行的信息,同时额外返回一个聚合结果或排序序号

经典题:

-- 按人口排名,ROW_NUMBER会生成无重复的排名 SELECT name, population, ROW_NUMBER() OVER (ORDER BY population DESC) AS rn FROM world; -- 按大洲分组,组内按人口排名 SELECT name, continent, population, RANK() OVER (PARTITION BY continent ORDER BY population DESC) AS rank FROM world;

ROW_NUMBERRANKDENSE_RANK这三个函数的区别是高频考点:

函数相同值排名后续排名举例
ROW_NUMBER不同,按顺序给号顺延1, 2, 3, 4
RANK相同,并列跳过后续1, 1, 3, 4
DENSE_RANK相同,并列不跳过1, 1, 2, 3

实际工作中,计算“每个部门工资最高的员工”这类需求,窗口函数比自连接和子查询要简洁得多:

SELECT name, department, salary FROM ( SELECT name, department, salary, RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rk FROM employees ) t WHERE rk = 1;

窗口函数刚接触的时候觉得抽象,但一旦理解了PARTITION BY就是“分组但不压缩行”这个点,绝大部分场景都能很快上手。

3. 做题过程中最常见的几个坑

3.1 GROUP BY与SELECT的字段限制

SQLZOO的SUM and COUNT模块里,有大量题目明明看着很简单,写出来却报错。最常见的报错就是:SELECT里出现了既不在GROUP BY中,也不是聚合函数的字段。

-- 错误 SELECT name, continent, COUNT(*) FROM world GROUP BY continent; -- 正确 SELECT continent, COUNT(*) FROM world GROUP BY continent;

第一条语句里,name既不在GROUP BY里面,也不是聚合函数,所以报错。现代数据库(比如MySQL的ONLY_FULL_GROUP_BY模式、PostgreSQL、SQL Server)都会直接拒绝这种写法。原因很简单:分组之后,每组里有多个name,数据库不知道该取哪一个。

我在实际工作中见过有人为了绕过这个问题,用ANY_VALUE(name)或者直接把name加进GROUP BY。前者要慎用,如果你不关心组里的具体值,那没问题;如果关心,就要想清楚你到底想取哪一条记录。后者则要小心:把name加入GROUP BY之后,分组的粒度会变得更细,聚合结果可能和你预期的完全不同。

3.2 WHERE和HAVING的边界

做题时经常有人问:“为什么这个过滤条件加WHERE不行,加HAVING才行?”答:看过滤的对象是行还是组。

  • WHERE:在分组前逐行过滤,不能使用聚合函数
  • HAVING:在分组后对组进行过滤,可以使用聚合函数

一个直观的例子:

-- 找出一个以上国家使用语言超过3种的大洲 SELECT continent FROM world GROUP BY continent HAVING COUNT(DISTINCT language) > 3;

这里的COUNT(DISTINCT language) > 3是在分组结果上判断的,必须用HAVING,用WHERE会直接报语法错误,因为WHERE阶段还没聚合,不知道COUNT是多少。

3.3 JOIN时条件写ON还是WHERE

这个坑在More JOIN模块里体现得很明显。JOIN的条件写在哪里,会影响最终结果。

  • ON:决定两个表如何匹配
  • WHERE:在匹配完成后的结果集上过滤

对于INNER JOIN,ON和WHERE的过滤逻辑是等价的,因为不符合ON条件的行根本不会被匹配进来。但对于LEFT JOIN,ON里的条件写在ON子句中,会保留左表的全部行,右表没有匹配到的以NULL填充;如果把条件放到WHERE里,那些右表为NULL的行就会被过滤掉。

-- LEFT JOIN + ON:返回所有教师,即使没有对应部门 SELECT teacher.name, dept.name FROM teacher LEFT JOIN dept ON teacher.dept_id = dept.id; -- LEFT JOIN + WHERE:会过滤掉没有部门的教师 SELECT teacher.name, dept.name FROM teacher LEFT JOIN dept ON teacher.dept_id = dept.id WHERE dept.name IS NOT NULL;

想清楚你要的结果是“保留左表全部行”还是“只保留能匹配上的行”,再决定条件放ON还是WHERE。这个看起来很小的区别,在一次统计报表里写错,结果数据就全偏了。

3.4 子查询返回多行时的处理

SQLZOO子查询模块里,最容易犯的错误就是用=去比较一个返回多行的子查询。SQL规范里,=只接受标量(单个值),子查询返回多行时要用INANYALL

-- 找出比欧洲所有国家GDP都高的国家 SELECT name FROM world WHERE gdp > ALL (SELECT gdp FROM world WHERE continent = 'Europe' AND gdp IS NOT NULL); -- 找出比欧洲至少一个国家GDP高的国家 SELECT name FROM world WHERE gdp > ANY (SELECT gdp FROM world WHERE continent = 'Europe' AND gdp IS NOT NULL);

ANYALL很多人在学习阶段很少用,但理解它们可以让你少写很多冗余的MAX/MIN子查询。注意子查询里通常要显式排除NULL,因为NULL参与比较会让结果变空或者不符合直觉。

3.5 NULL参与计算时的结果偏移

Using Null模块设计的题目,就是逼你意识到NULL的存在。比如统计某字段总和时,NULL会被直接忽略,而不是当作0。这在累加金额、汇总数量时尤其危险:

-- 如果amount字段有NULL,SUM的结果是不包含这些NULL行的 SELECT SUM(amount) FROM orders;

要区分“NULL被忽略”和“NULL当作0”两种情况。如果业务上NULL表示“无数据”,SUM忽略它通常没问题;但如果NULL表示“欠款未录”,那SUM的结果就会偏小。这时候需要用COALESCE把NULL转成0再聚合:

SELECT SUM(COALESCE(amount, 0)) FROM orders;

4. 做完SQLZOO之后,这些方向值得继续深挖

4.1 慢SQL优化:EXPLAIN主要看什么

SQLZOO帮你解决了“怎么写SQL”的问题,但实际工作中还有一关:SQL写对了还不够,还得跑得快。热搜词里的“慢SQL优化 explain主要看哪些信息”,就是这个方向的核心。

一条SQL执行慢,第一时间用EXPLAIN看执行计划。主要看四个信息:

指标含义关注点
type访问类型从好到差依次是system > const > eq_ref > ref > range > index > ALL,避免ALL全表扫描
key实际使用的索引是否为NULL
rows预估扫描行数数字越小越好
Extra附加信息出现Using filesort、Using temporary要警惕

最常见的慢SQL原因就是:WHERE条件或JOIN条件没有走索引,导致全表扫描。解决办法也很直接:给索引列加索引,避免对索引列做函数运算(WHERE YEAR(create_time) = 2024会导致索引失效,应写成WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01'),避免使用前置通配符的LIKE(LIKE '%abc'无法走索引)。

还有一类慢SQL是“大表JOIN小表”的顺序问题。SQL优化器一般会自动调整,但如果你用了复杂的子查询,优化器可能选错执行顺序。这种情况下,把子查询改成JOIN、或者强制指定驱动表,经常能立竿见影。

4.2 SQL去重:DISTINCT还是GROUP BY

热搜词里有大量的“sql语句去重查询”,说明这是日常开发的高频需求。去重的两种主流写法:

-- 方式一:DISTINCT SELECT DISTINCT customer_id FROM orders; -- 方式二:GROUP BY SELECT customer_id FROM orders GROUP BY customer_id;

执行结果上,如果只是对单个字段或一组字段去重,两者语义相同。但GROUP BY可以做更复杂的事情:去重的同时统计每个分组的数量、保留其他聚合结果。DISTINCT则更“纯粹”,可读性更好。

还有一个高频需求是“去重后统计数量”:

-- 统计有多少个不同的客户下了单 SELECT COUNT(DISTINCT customer_id) FROM orders;

注意COUNT(DISTINCT field)对NULL的处理:NULL不会计入COUNT,如果业务上需要把NULL也算作一个值,要用COUNT(DISTINCT COALESCE(field, 'unknown'))。

4.3 SQL注入:写SQL时必须有的一根弦

热搜词里“sql注入万能密码绕过”“ctfshow web入门 sql注入”这些都是安全方向的话题。虽然SQLZOO不教这个,但作为一个经常写SQL的人,必须知道SQL注入是怎么回事,否则你写的接口可能就是别人攻击的入口。

SQL注入最常见的原理是:外部输入直接拼进SQL语句,导致输入被当作SQL代码执行。比如:

-- 危险:直接把用户输入拼进SQL SELECT * FROM users WHERE username = 'admin' AND password = 'xxx'; -- 如果用户名输入 `admin' -- `,密码随便填,这就变成了 SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'xxx';

--是SQL的注释符,后面的内容全被注释掉了,攻击者不需要知道密码就能登录。防御手段的核心是“参数化查询”,让数据库把输入当成数据而不是代码:

// 用PreparedStatement,不要字符串拼接 PreparedStatement ps = conn.prepareStatement( "SELECT * FROM users WHERE username = ? AND password = ?" ); ps.setString(1, username); ps.setString(2, password);

平台上的SQLZOO是纯练习环境,不存在注入风险。但养成“任何外部输入都不直接拼SQL”的习惯,是每个SQL使用者走向专业必须迈过的一道坎。

4.4 SQL Server相关:版本选择与安装避坑

热搜词里大量出现“SQL Server 2019安装教程”“SQL Server 2022下载”“SQL Server 2008 R2”等词,说明不少人在学习SQLZOO之后,想在自己电脑上搭一套数据库环境练手。这里有个建议:如果是新学SQL,直接装最新稳定版SQL Server 2022 Developer版本,功能和企业版一样,免费,只是不能用在生产环境。2008 R2和2012虽然在一些旧公司还在用,但2008 R2早已停止主流支持,新环境不建议装。

安装过程中最常见的坑有三个:

  • 安装时报“无法启动 Windows Management Instrumentation (WMI) 服务”,先检查Windows服务里WMI是否被禁用。
  • 卸载报“警告26003:无法卸载安装程序支持文件”,多是因为残留了旧版本,先清理干净再装新的。
  • 装完之后连接不上,大多数情况是SQL Server的TCP/IP协议没启用,在“SQL Server配置管理器”里把TCP/IP启用并重启服务就好了。

4.5 窗口函数的实战拓展

窗口函数是SQLZOO里比较新也比较难啃的模块,但它绝对是实际工作中投入产出比最高的SQL能力。举几个高频实战场景:

  • 排名:每个部门工资排名TOP3
  • 同比环比:计算每个月销售额与上个月的差额,用LAG函数
  • 累计求和:计算每日累计销售额,用SUM(...) OVER (ORDER BY day)
  • 分组TopN:每个分类下销量前5的商品
-- 每个月销售额与上个月的差额 SELECT month, sales, LAG(sales, 1) OVER (ORDER BY month) AS prev_sales, sales - LAG(sales, 1) OVER (ORDER BY month) AS diff FROM monthly_sales;

窗口函数熟练之后,会发现它能替代掉大量原来要写自连接、写复杂子查询的场景,代码更短,性能也更好。这也是为什么现在数据分析师、后端开发的面试题里,窗口函数几乎成了必考点。

5. 给你的刷题顺序和学习建议

最后聊一点我的实际体会。

SQLZOO这套题,我见过不少人的刷法是:从第一题一路往后做,卡住了就搜答案,看完了就继续下一题。这样做完一遍,感觉都会了,但过一周再来看,又全忘了。原因很简单:做题的时候缺少一个“主动提取”的过程。

我的建议是:每做完一个模块,合上答案,自己重新写一遍这个模块里出错最多的几道题,然后自言自语讲一遍——这道题考的是什么知识点,为什么要这么写。讲得出来,才说明你真的掌握了,这比闷头刷完100道题有用得多。

另外,SQLZOO的英文原版里,每个模块前面有一段简短的知识点介绍,中文版的翻译虽然能看懂,但有些术语还是建议对照英文看一遍,比如“scalar subquery”“correlated subquery”“window frame”。因为在查文档、看面试题的时候,大部分资料都是英文的,提前把这些术语混个脸熟,后面会顺畅很多。

刷完这套题之后,你的SQL地基就算打牢了。接下来可以去找真实业务数据动手练,比如把公司某个报表需要的SQL写出来、去分析一条线上慢SQL的执行计划、或者尝试用窗口函数重写一段以前写得特别别扭的查询。SQL这门手艺,光看不练永远是纸上谈兵,写得多、错得多、再改得多,才是真正变成自己的东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询