news 2026/11/3 20:51:15

《SQL基础教程》PDF学习路径:执行顺序、JOIN与NULL避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
《SQL基础教程》PDF学习路径:执行顺序、JOIN与NULL避坑

1. 这本日系入门书凭什么被一传再传

我最初翻到《SQL基础教程》这本 PDF,是在带一个完全没碰过数据库的新同事时。他问的问题非常典型:SELECT后面到底能写什么、GROUP BY和HAVING到底谁管谁、为什么两个表连起来之后行数会变多。我翻了七八本号称“零基础”的 SQL 书,多数要么一上来就讲关系代数,要么把语法条目罗列成字典,读者看完能背却写不出。这本日系教材的写法不太一样:它把每一个语法点都拆成“先有问题、再给图解、最后给可运行语句”三段,读者跟着敲一遍,脑子里能留下画面。PDF 这种载体其实很适合它,因为书里大量的表结构示意图、执行顺序对照表,翻页查阅比纸质书更顺手,做练习时扔在副屏上对照着写语句,效率很高。

这篇内容我想聊的不是“推荐一本书”这么简单,而是把它当成一条学习路径的骨架来拆:它讲了什么、为什么这么讲、你照着学要配什么环境、哪些章节是真正的分水岭、哪些细节书上一笔带过但实际工作里会反复咬你。适合三类人看:完全零基础想系统入门的、写了两三年 CRUD 但一直靠搜语句拼凑的、以及带新人却不知道怎么安排顺序的。全文的实操部分我都在两个开源关系型数据库上验证过,样例数据自己重写了一套,你可以直接抄。

1.1 图解式写法解决的到底是什么问题

SQL 这门语言有个很反直觉的特点:它的书写顺序和执行顺序不一致。你写的第一行是SELECT,但数据库最先处理的往往是FROM。新手写错语句,十次有六次是脑子里按书写顺序推演执行过程导致的。这本书最值钱的地方,就是用大量图示把“数据是怎么一步步被筛、被分组、被排序”画出来了。

举个最常见的例子。下面这条语句,很多人会以为先选出列再过滤:

SELECT product_type, COUNT(*) FROM product WHERE product_type <> '衣服' GROUP BY product_type HAVING COUNT(*) >= 2;

实际的处理链条是:先从product取出全部行,再用WHERE逐行筛掉不需要的行,然后按product_type把剩下的行分成若干组,接着用HAVING把不满足条件的组整组丢掉,最后才在SELECT里计算要输出的列并投影。你把这条链在脑子里过一遍,就会明白为什么WHERE里不能写聚合函数——那时候组还没形成,聚合无从谈起。

我在实际带人时的做法是:让他把这条语句的六个关键字按执行顺序重新抄一遍,抄在纸上,贴在显示器边上。一周之后基本就不会再问“为什么这里报错”了。这个动作看起来笨,但它把抽象的执行模型变成了肌肉记忆,比看十页文字解释都管用。

提示:书里给的执行顺序图建议自己手绘一遍,不要只是看。手绘的过程会逼你思考每一步的输入和输出分别是什么形状的数据(单行、行集、还是分组后的组集)。

1.2 谁读得进去,谁读不进去

这本书的定位很清晰:面向完全没写过 SQL、但需要马上能用的读者。它的深度停在“能正确写出查询”这一层,对执行计划、索引结构、锁机制基本不展开。所以它适合的人也很明确:

  • 刚转行做数据相关岗位,需要两周内能独立写查询的;
  • 后端开发写了很久 CRUD,但JOIN一多就心里没底的;
  • 做运营、产品、财务分析,需要自己从数据库里捞数的人;
  • 带新人的人,拿它当教学大纲用。

不适合的人同样明确:已经在写复杂报表、需要调优慢查询的,这本书给不了你答案,它连EXPLAIN都没怎么提。索引、分区、执行计划这些要另找资料。我个人建议把它当作“第一本”,读完立刻接一本讲性能或者讲数据仓库的资料,中间不要停太久,否则语法会忘,而语法忘了再捡起来成本很低,思维模型忘了再重建就很贵。

还有一个容易被忽略的点:这本书的练习题质量高于正文。正文是讲解,练习题是把讲解拆开重组,很多题需要你把两三个语法点组合起来才能做出来。我见过不少人正文读得很顺,练习题跳过,结果一上手真实需求就卡住。练习题不是可选项,它是这本书的另一半。

1.3 版本差异与 PDF 使用的现实问题

这本书有几个版本,后出的版本在几个地方做了实质补充:窗口函数、GROUPING运算符、事务的隔离级别、以及外连接写法的调整。窗口函数这一块尤其重要,现在做排名、累计求和、同比环比,基本离不开它,早期版本是没有的。所以你手上的 PDF 如果是老版本,建议至少把窗口函数那一章单独补齐,否则会在实际工作里明显吃亏。

PDF 载体本身也有取舍。好处是搜索快、可以开双栏对照、复制示例语句方便;坏处是很多人会不自觉地“滑过去”,眼睛看完了手没动。我的建议很直接:PDF 只用来查阅和对照,正文第一遍一定要自己敲。敲的时候故意改一下表名、改一下列名、改一下条件值,让语句报错,再看错误信息,这个过程的记忆强度远高于复制粘贴。

另外提醒一句,网上流传的 PDF 版本排版质量参差,有些扫描版表格线错位,图里的箭头和字段对不上,看着会非常痛苦。如果发现图表模糊到看不清字段名,别硬扛,换一个清晰版本,或者干脆照着正文文字自己重画表结构,画一遍反而理解更深。

2. 动手之前:把环境和样例数据先落地

很多人读 SQL 书的失败点不在语法,而在第一天就没跑起来。装数据库、建库、建表、灌数据,这四步任何一步卡住,后面全废。所以这一节我按“最短可用路径”来写,目标是在半小时内让你有一条能跑通的查询语句。

2.1 数据库选型:跟着书里的双版本对照走

这本书的一个特点是同时给出两种主流开源关系型数据库的写法对照,因为两者在细节上有差异:字符串拼接、日期函数、LIMIT的写法、隐式类型转换的严格程度、INTERSECT和EXCEPT的支持情况,都不一样。我的建议是主用一个,另一个装上看一眼。

主用哪个?如果你的学习目标是练语法、写查询、做分析,选哪个都行,我倾向选标准兼容性更好的那个,因为书里大部分语句可以直接跑,报错少,挫败感低。如果你的工作环境已经确定了数据库类型,那就直接用它,不要为了“学得纯粹”去装一个用不上的。

装的方式我推荐容器,一条命令起一个实例,用完删掉,不污染本机:

# 以容器方式起一个本地实例,端口映射到本机,数据卷挂到当前目录 docker run -d --name sql-learn \ -e POSTGRES_PASSWORD=learn123 \ -p 5432:5432 \ -v "$PWD/pgdata:/var/lib/postgresql/data" \ postgres:16

注意:容器里的数据默认随容器删除而消失,-v挂载这一步别省,否则你辛苦灌的样例数据在重启后就没了。我第一次搭环境就是漏了这个,第二天打开发现表全空,一度以为自己建错了库。

装完先验证连接,再谈建表。客户端工具方面,命令行客户端最轻,适合练手;图形化工具适合看表结构和结果集,两者都留着,别只用一个。命令行能逼你记住语法,图形化能让你快速确认数据长什么样。

2.2 建表与样例数据:自己写一遍比导入强

书里配了一套样例数据,名字是日文表名。我的做法是改成自己能记住的英文表名重写一遍,这个过程本身就是练习。下面是我用的一套简化版,字段类型和约束都保留了,足够覆盖全书绝大部分例子:

CREATE TABLE product ( product_id CHAR(4) NOT NULL, product_name VARCHAR(100) NOT NULL, product_type VARCHAR(32) NOT NULL, sale_price INTEGER, purchase_price INTEGER, regist_date DATE, PRIMARY KEY (product_id) ); INSERT INTO product VALUES ('0001', 'T恤', '衣服', 1000, 500, '2024-09-20'), ('0002', '打孔器', '办公用品', 500, 320, '2024-09-11'), ('0003', '运动T恤', '衣服', 4000, 2800, NULL), ('0004', '菜刀', '厨房用具', 3000, 2800, '2024-09-20'), ('0005', '高压锅', '厨房用具', 6800, 5000, '2024-01-15'), ('0006', '叉子', '厨房用具', 500, NULL, '2024-09-20'), ('0007', '擦菜板', '厨房用具', 880, 790, '2024-04-28'), ('0008', '圆珠笔', '办公用品', 100, NULL, '2024-11-11');

注意这里有两个purchase_price是NULL,两个regist_date是NULL,这是故意留的。全书关于NULL和三值逻辑的坑,全靠这几行数据才能演示出来。如果你把NULL都填上值,后面那几节你会觉得“书在讲废话”,因为你永远复现不了问题。

再建一张用于练连接的表:

CREATE TABLE shop_product ( shop_id CHAR(4) NOT NULL, shop_name VARCHAR(200) NOT NULL, product_id CHAR(4) NOT NULL, quantity INTEGER NOT NULL, PRIMARY KEY (shop_id, product_id) ); INSERT INTO shop_product VALUES ('000A', '东京', '0001', 30), ('000A', '东京', '0002', 50), ('000B', '大阪', '0003', 15), ('000B', '大阪', '0004', 50), ('000B', '大阪', '0006', 10), ('000B', '大阪', '0007', 20), ('000C', '福冈', '0003', 20), ('000C', '福冈', '0006', 30);

这张表里故意有一个商品(0005高压锅)在店铺表里没有出现。等你写外连接的时候,这条“孤儿数据”就是判断你有没有真正理解LEFT JOIN的试金石。

心得:建表时字段类型别嫌麻烦,CHAR和VARCHAR的区别、INTEGER和DECIMAL的区别,在书里有专门一节讲,你按书上的写一遍,比读三遍解释记得牢。我当初把sale_price建成CHAR,结果SUM算出来完全不对,查了半天才发现是字符串拼接。

2.3 查询习惯:从第一天就养成三个动作

环境跑通之后,有三个习惯建议从第一天就养成,它们决定了你后面学得顺不顺。

第一,每次执行前先想结果形状。这条语句返回几行几列?有没有重复?有没有NULL?想完再按回车,然后对照实际结果。这个动作看起来慢,实际上是最快的反馈循环。

第二,写完语句马上读一遍错误信息。新手最常见的浪费是把报错截个图发群里问,而错误信息里通常已经写清楚了:哪一行、哪个字段、期望什么类型。我带的那个新人,前两周问我的问题里有一半是GROUP BY违规,我让他自己读一遍错误提示,从此再没问过。

第三,给每条语句加注释。哪怕只是一行-- 按品类统计销售额,排除价格为空的行,一个月后回来看,你会感谢自己。练习题阶段积累下来的语句,加上注释就是你的个人语法库,比任何速查表都好用。

3. 全书骨架拆解:四个真正的能力台阶

这本书的章节顺序不是随便排的,它暗含了四个台阶:单表查询、聚合分组、多表连接、子查询与集合运算。每个台阶对应一种思维方式的切换,跨不过去就会在某个地方卡很久。我把每一层的核心难点和突破方式整理如下。

3.1 第一台阶:把执行顺序刻进脑子

第一层是单表查询,SELECT、WHERE、ORDER BY、DISTINCT、LIMIT。语法本身没什么门槛,难的是几件事:

DISTINCT作用于整行而不是某一列。很多人以为SELECT DISTINCT product_type, sale_price是对第一列去重,实际上它是对“这两列组成的组合”去重。要验证这一点,拿上面的数据跑一遍,看看结果行数就知道。

ORDER BY里可以用SELECT中定义的别名,但WHERE里不行。原因还是执行顺序:WHERE执行的时候SELECT还没跑,别名还不存在。这个问题我被问过不下十次,每次我都让人家自己去试,报错一次就记住了。

NULL排序的位置在两种数据库里规则不同,有的默认排在最前,有的排在最后。要稳定控制顺序,得显式写明NULLS FIRST或配合CASE使用。这一点书上可能只提了一句,但报表里排序错乱十有八九是这个原因。

还有一个容易被跳过的是LIMIT与排序的配合。不加ORDER BY直接LIMIT 10,返回的十行是“数据库碰巧给你的十行”,不保证稳定。翻页查询如果不带排序键,会出现某条数据在两页里都出现或者一页都不出现的情况。这个坑我在真实项目里见过,排查了大半天。

3.2 第二台阶:从“行”切换到“组”

聚合函数和GROUP BY是第一个真正的分水岭。前面你面对的都是行,到这里突然要面对“组”,思维要切换。

COUNT(*)和COUNT(列名)的区别必须彻底搞清:前者数行数,后者数该列非NULL的个数。以上面的数据为例,COUNT(*)返回 8,COUNT(purchase_price)返回 6。这个差异在做数据质量检查时天天用到——它是最快的“空值率”统计方法。

SUM和AVG会忽略NULL,AVG的分母是非NULL的行数而不是总行数。这一条如果没记住,算出来的平均值会系统性偏高。要统计包含NULL行的平均,得用SUM(x) / COUNT(*)手动算。

分组键可以写多个,GROUP BY a, b表示按组合分组。这里有个标准规定:SELECT子句里只能出现分组键和聚合函数,其他列不允许出现。某些数据库在宽松配置下会让你写,然后返回一个“不确定是哪一行”的值,表面上不报错,实际结果是错的。这类错误最难查,因为它不报错。

HAVING和WHERE的分工要清晰:WHERE筛行,HAVING筛组。一个常见误区是把本该写在WHERE的条件塞进HAVING,比如HAVING product_type = '衣服'。语法上跑得通,但逻辑上多了一步:先分成所有组,再把不要的组丢掉。写在WHERE里是先把行筛掉再分组,数据量小的时候没差别,数据量大的时候差距很明显。

提示:判断一个条件该写WHERE还是HAVING,看它依赖不依赖聚合结果。不依赖聚合函数的,一律放WHERE。这条规则简单粗暴,但几乎不会错。

3.3 第三台阶:表连接才是真正的分水岭

到了多表连接,很多人会明显感觉到难度上来了。内连接相对直观,LEFT JOIN开始有迷惑性,ON和WHERE的分工则是全书最容易被读漏的一节。

先明确内连接在做什么:它取两张表的笛卡尔积,然后按ON条件筛选。所以两张表连接后行数变多是正常的,不是 bug。如果连接键在某一侧不唯一,就会出现一对多放大,这是报表数据翻倍的最常见原因。做连接前先确认连接键是否唯一,是我现在写任何连接语句的第一动作。

外连接的区别在于保留哪一侧的全部行。LEFT JOIN保留左表全部行,右表没匹配上的地方补NULL。用上面的数据跑:

SELECT p.product_id, p.product_name, s.shop_name, s.quantity FROM product p LEFT JOIN shop_product s ON p.product_id = s.product_id ORDER BY p.product_id, s.shop_id;

你会看到0005高压锅那一行,shop_name和quantity都是NULL。这条“孤儿数据”能出来,说明外连接写对了。如果换成INNER JOIN,这一行就消失了。这个对比一定要亲手做一遍,比看十张示意图都直接。

关键的坑在这里:ON里的条件只影响“匹配”,WHERE里的条件影响“最终保留”。把右表的过滤条件写进WHERE,外连接会退化成内连接,因为右表补出来的NULL行会被WHERE直接筛掉。正确写法是把右表的条件放进ON:

-- 写法一:条件在 ON 里,保留左表全部行,右表只匹配数量大于 20 的记录 SELECT p.product_id, p.product_name, s.shop_name, s.quantity FROM product p LEFT JOIN shop_product s ON p.product_id = s.product_id AND s.quantity > 20; -- 写法二:条件在 WHERE 里,外连接退化为内连接 SELECT p.product_id, p.product_name, s.shop_name, s.quantity FROM product p LEFT JOIN shop_product s ON p.product_id = s.product_id WHERE s.quantity > 20;

两条语句的结果行数不一样,自己跑一遍对比,这个知识点就再也不会忘。我在评审别人写的报表 SQL 时,这两个写法的混用是出现频率最高的问题,没有之一。

另外要留意CROSS JOIN,它是纯笛卡尔积,不带任何条件。书上会讲它,实际工作里用得不多,但在生成日期维度、构造测试数据时很有用。知道它存在即可,别在日常查询里误用。

3.4 第四台阶:子查询、集合运算与窗口函数

子查询分两类:普通子查询和相关子查询。前者的子查询可以独立执行,后者依赖外层查询的每一行。相关子查询理解起来绕,但它是EXISTS的基础,也是很多人写不出“找出某类中最大的那条记录”的原因。

EXISTS的语义是“子查询是否返回至少一行”,它只关心有没有,不关心返回什么。所以EXISTS里的SELECT写成SELECT *还是SELECT 1都一样,性能上没有区别。这个点经常被误解,有人以为SELECT 1更快,其实数据库优化器会把列清单直接忽略掉。

集合运算有三个:并集、交集、差集。并集默认去重,加ALL不去重。交集和差集在部分数据库中支持不完整,需要改用JOIN或NOT EXISTS模拟。这个差异在实际工作里很常见,你写出来的语句在本地能跑,换到另一个环境就报错,原因往往就是这里。养成习惯:用之前先确认目标数据库支持不支持。

窗口函数是后出版本才补进来的,但它的价值极高。它和GROUP BY的区别在于:聚合会压缩行数,窗口函数不会。想在每一行后面附上“该品类的平均价格”“按销售额的排名”“累计求和”,都得用它:

SELECT product_id, product_name, product_type, sale_price, RANK() OVER (PARTITION BY product_type ORDER BY sale_price DESC) AS rnk, SUM(sale_price) OVER (PARTITION BY product_type) AS type_total, SUM(sale_price) OVER (ORDER BY sale_price ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total FROM product WHERE sale_price IS NOT NULL;

PARTITION BY是分组但不压缩,ORDER BY决定窗口内的顺序,ROWS BETWEEN定义滑动范围。这三件套搞明白,日常报表里一大半的计算需求都能自己写出来,不用再回头找开发帮忙。

4. 那些年一起踩过的坑:书上一笔带过但很要命

书里为了保持节奏,有些细节只有一两句话,但这些恰恰是实际工作中最容易出事的地方。这一节我按“现象、原因、处理方式”的结构整理,都是我或者身边的人真实撞过的。

4.1 NULL 不是零,也不是空字符串

NULL表示“未知”,它不参与任何常规比较。sale_price = NULL永远不成立,sale_price <> NULL也永远不成立,必须写IS NULL或IS NOT NULL。这个规则大家都知道,但真正会咬人的是NOT IN。

-- 期望找出不在某集合里的商品,但子查询里有 NULL,结果会返回空集 SELECT product_id, product_name FROM product WHERE product_id NOT IN (SELECT product_id FROM shop_product);

如果子查询结果里出现NULL,整个NOT IN的比较结果就变成“未知”,一行都返回不了。这个问题在数据量小的时候容易发现,数据量大的时候你会以为是业务数据本身的问题,排查方向就跑偏了。稳妥写法是用NOT EXISTS:

SELECT p.product_id, p.product_name FROM product p WHERE NOT EXISTS ( SELECT 1 FROM shop_product s WHERE s.product_id = p.product_id );

EXISTS只判断“有没有行”,天然避开三值逻辑的坑。我在写“排除某类数据”的查询时,现在基本只用NOT EXISTS,除非明确知道子查询列不会有NULL。

4.2 聚合函数遇到 NULL 会静默丢行

AVG忽略NULL这个行为,如果不注意会造成报表口径错误。比如算客单价,如果金额字段有空值,AVG(amount)算的是“有金额的订单的平均”,而不是“全部订单的平均”。这两个口径在业务上是两回事,报给业务方的时候会被质疑。

处理方式有两种:口径明确要求包含空值时,用SUM(amount) / COUNT(*);口径要求排除空值时,在WHERE里显式过滤,让逻辑写在明面上。不要依赖函数的隐式行为,这是我从一次口径争议里学到的:当时双方对着同一个字段报出两个数,最后发现就是AVG把空值排除了。

4.3 HAVING 里放下标条件,逻辑对但效率差

前面提过WHERE和HAVING的分工,这里补一个实操层面的点:HAVING的作用对象是分组结果,所以它必须在分组完成之后才能执行。把本该在WHERE里做的过滤放到HAVING,数据库就要先对全量数据分组,再丢弃大部分组,中间过程的开销白白浪费。

我做过一个粗略对比:一张千万级的表,把品类过滤从HAVING挪到WHERE,查询时间从十几秒降到一秒多。当然具体数字取决于数据分布和数据库实现,但方向是确定的。能放WHERE的条件绝不放HAVING,这条规则不需要犹豫。

4.4 相关子查询能跑通,但要有性能直觉

相关子查询的写法很优雅,表达力强,但它对每一行外层数据都要执行一次子查询,数据量一大就撑不住。书里讲相关子查询时重点是语法,性能那部分提得不多,所以很多人会写出“能跑但对数据库很不友好”的语句。

我的经验是:相关子查询适合“数据量不大、逻辑确实需要逐行判断”的场景。如果发现语句在百万级数据上跑不动,第一反应应该是看能不能改成JOIN或者窗口函数。同样一个“取每组最大值对应的整行”的需求,相关子查询、JOIN子查询、窗口函数三种写法,性能差距可以到几倍甚至更多。

心得:写完之后习惯性地问自己一句“这条语句如果数据量翻一百倍还能跑吗”。问不出来答案,就去学EXPLAIN,这是从“会写”跨到“写得好”的必经之路。

4.5 几个小坑,攒在一起说

字符串比较受排序规则影响,大小写敏感与否、全角半角是否等价,不同配置下结果不同。做唯一性校验时如果依赖字符串比较,务必先确认排序规则。

隐式类型转换会让索引失效,也会产生意外结果。字段是字符串类型,你传数字去比较,数据库可能把整列转成数字再比,索引就用不上了。显式写CAST或者保证类型一致,是好习惯。

UNION默认去重,需要保留重复行必须写UNION ALL。去重是有成本的,需要排序或哈希,数据量大时差距明显。如果业务上确定不会有重复,直接写UNION ALL。

事务这块,书里讲了提交和回滚,但实际使用中最容易忘的是:很多客户端默认自动提交。你执行一条UPDATE觉得还没提交,其实已经生效了。练习时建议先关掉自动提交,手动控制,把BEGIN、COMMIT、ROLLBACK三个动作走一遍,感受一下回滚的效果。

5. 把书读成自己的能力:练习与延伸

书读完了,语法记住了,这时候最容易出的问题是“例题都会,需求不会”。原因很简单:例题已经把问题拆好了,真实需求需要你自己拆。这一节讲怎么补上这一步。

5.1 练习题做两遍,第二遍改条件

第一遍按书上要求做,对答案。第二遍把条件改掉:把“按品类统计”改成“按品类和登记年份统计”,把“销售额前 3”改成“每个品类前 3”,把“内连接”改成“左连接看看结果差在哪”。改条件的过程就是逼自己理解每个子句的作用边界。

我自己的做法是准备一个practice.sql文件,每道题写两版:标准答案版和魔改版,旁边用注释写清楚“改了哪里、结果为什么变”。这个文件攒到三四十条的时候,你会发现它比任何速查表都好用,因为里面的每一条都是你自己踩过、想过的。

5.2 拿身边真实的问题反推语句

光做习题还是不够,得找真实问题。身边的真实问题其实很多:自己的记账记录、跑步数据、读书清单、购物订单,随便哪个整理成一张表,然后问自己一些问题。

比如把一年的记账记录整理成一张表,字段是日期、类别、金额、备注。然后试着回答:每个月总支出多少、哪一类占比最高、环比变化如何、有没有连续三个月超预算的类别、单笔金额排前五的是哪些。这些问题会自然逼出GROUP BY、窗口函数、子查询、日期函数,而且是带着目的去用,记忆效果完全不同。

这一步的价值在于:你会开始遇到“语法没错但结果不对”的情况,而这种排查能力才是真正的工作能力。例题永远不会错,真实数据永远会错。

5.3 从语法走向执行计划

这本书的终点大概在“能正确写出查询”。再往上走一步,就是理解数据库怎么执行你的语句。切入点是EXPLAIN:在你写的语句前面加上它,看看数据库给出的执行路径。

EXPLAIN SELECT product_type, SUM(sale_price) FROM product WHERE regist_date >= '2024-09-01' GROUP BY product_type;

输出里会告诉你用了什么扫描方式、有没有走索引、估算行数是多少。刚开始看不懂很正常,先关注两件事:有没有全表扫描、估算行数和实际行数差多少。这两点能解释大部分“为什么这条语句慢”的问题。

再往后是索引。索引的本质是用空间换时间,代价是写入变慢、占用存储。什么时候该建、建在哪些列上、联合索引的列顺序为什么重要,这些内容任何一本 SQL 入门书都讲不透,需要单独找资料。但只要你把这本书的语法基础打牢,这部分学起来会很快,因为你知道自己要优化的是哪种查询。

6. 常见问题速查表

下面这张表是我带人过程中被问得最多的问题,按出现频率排序。遇到卡壳先扫一眼,多数情况能直接对上。

现象常见原因处理方式
SELECT里的列报“不是聚合函数也不是分组键”违反了分组查询的列限制把该列加进GROUP BY,或对它套聚合函数
WHERE里写聚合函数报错WHERE执行时分组尚未形成把聚合条件移到HAVING
LEFT JOIN结果和INNER JOIN一样右表条件写在了WHERE里把右表条件移到ON子句
连接后行数暴涨连接键在某一侧不唯一,产生一对多放大先确认连接键唯一性,必要时先去重再连接
NOT IN返回空结果子查询结果里含NULL改用NOT EXISTS,或在子查询里过滤NULL
COUNT(列)比COUNT(*)小该列存在NULL,COUNT(列)不计入明确口径,需要数行就用COUNT(*)
平均值偏高AVG忽略了NULL,分母变小改用SUM(x) / COUNT(*)手动计算
排序结果里NULL位置飘忽不同数据库对NULL排序默认规则不同显式指定空值排前或排后
翻页出现重复或漏数据LIMIT没配稳定排序键加上唯一性排序键再分页
并集结果少了重复行UNION默认去重需要保留重复改用UNION ALL
差集语句换个环境报错部分数据库不支持差集运算符用NOT EXISTS或反连接模拟
字符串比较结果不符合预期排序规则影响大小写、全半角判断确认排序规则,必要时统一转换后再比
条件里数字和字符串混用,查询变慢隐式类型转换导致索引失效显式转换,保证两侧类型一致
UPDATE执行后无法回滚客户端处于自动提交模式关闭自动提交,手动BEGIN/COMMIT

这张表建议收藏,但别只收藏。真正有效的是你自己往里加行——每踩一次坑,补一行,半年之后它就是你个人最有价值的资产。

最后分享一个小习惯:我在读完任何一本技术书之后,会强迫自己用一句话概括它解决了什么问题。这本书的答案是“让一个没写过 SQL 的人,能独立写出正确的多表查询”。这个定位非常清楚,所以它在该深的地方深,该浅的地方果断浅。你按这个定位去用它,把语法和思维模型打牢,剩下的性能、架构、数据治理,交给下一本书。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 20:49:55

把“事后复盘”变成职业决策资产库,我做了这套系统

每个人都是事后诸葛亮&#xff0c;但很少有人把“事后”变资产。今天聊的“hindsight”&#xff0c;中文直译叫“后见之明”&#xff0c;听起来带着点自嘲——事情结束了才看懂&#xff0c;早干嘛去了&#xff1f;但真正把它做成一套系统&#xff0c;你会发现这是最被低估的生产…

作者头像 李华
网站建设 2026/11/3 20:49:55

从“事后聪明”到工程回溯:hindsight在复盘与故障排查中的实践

我们复盘一次线上故障&#xff0c;发现根因居然在半年前的一次"临时方案"里埋着。翻 commit 记录时&#xff0c;那句注释写得清清楚楚&#xff1a;"这里先打个补丁&#xff0c;后面空了再重构。"后面一直没空。直到凌晨两点&#xff0c;它在我们最忙的时候…

作者头像 李华
网站建设 2026/11/3 20:49:01

手机行业创新困局:从硬件迭代到用户体验的转型思考

1. 项目概述&#xff1a;手机行业的创新困局与破局思考最近在科技圈看到一个很有意思的观点&#xff1a;某业内人士提出手机厂商应该暂停新机发布&#xff0c;转而专注老产品维护和开发"手机龙虾"这类创新产品。这个说法乍听有些反常识&#xff0c;但细想之下确实戳中…

作者头像 李华
网站建设 2026/11/3 20:48:58

突破思维定式:认知重构与战略思考实战指南

1. 思维牢笼的本质解析 "思维里的墙"这个概念最早由心理学家提出&#xff0c;指的是人们在认知过程中形成的固定思维模式和心理障碍。这些无形的限制往往比现实中的困境更难突破&#xff0c;因为它们已经内化为我们看待世界的默认方式。 我在心理咨询工作中接触过大…

作者头像 李华
网站建设 2026/11/3 20:48:54

云服务器上从零部署MySQL:规划、安装、调优与运维全链路

云主机上装数据库这件事&#xff0c;说简单也简单&#xff0c;说容易翻车也是真容易。我见过太多人开好实例、连上终端、敲完一条安装命令就以为万事大吉&#xff0c;结果应用连不上、远程连不了、重启之后服务没起来、磁盘悄悄写满——问题全堆在后面。这篇就把在云主机&#…

作者头像 李华
网站建设 2026/11/3 20:48:53

Redis集群架构与主从复制实战指南

1. Redis集群模式深度解析Redis作为当今最流行的内存数据库之一&#xff0c;其集群方案的选择直接影响系统的高可用性和扩展性。我在实际生产环境中测试过多种Redis集群方案&#xff0c;发现每种模式都有其特定的适用场景和实现细节。1.1 原生Redis Cluster架构Redis官方提供的…

作者头像 李华