资讯动态

SQL必知必会前15章核心笔记:从基础查询到多表联结的实战要点

发布时间:2026/10/20 8:10:26 来源:尧图企业网站定制
《SQL必知必会》这本书我在不同阶段翻过好几遍每次都有新的收获。第1~15章恰好覆盖了SQL最核心的查询语法从最简单的SELECT取数到多表联结、子查询、分组汇总再到INSERT写入几乎把日常开发里90%的SQL场景都讲透了。这篇文章是我基于这本书前15章的读书笔记整理重点不是复述原书内容而是把书里容易一笔带过但实际经常踩坑的地方展开讲清楚配合一些我平时写SQL的经验和习惯给正在学SQL或者想巩固基础的朋友做个参考。很多初学者有个误区觉得SQL就是背几条语法能跑出结果就行。真到面试或者写业务需求的时候才发现性能、NULL处理、联结顺序、分组语义这些细节才是拉开差距的地方。第1~15章看着基础其实埋了大量值得深挖的点这篇文章就是围绕这些点展开的。1. 从环境到第一条SELECT基础与认知建立1.1 环境选型与建表准备原书第1章主要讲SQL是什么、数据库是什么内容偏概念。如果你已经装了数据库客户端可以跳过直接看语法。但如果你是第一次接触SQL我建议先动手搭建一个练习环境不然光看概念很容易飘。我自己的选择是在本机装一个轻量级的数据库服务用书里的示例数据建表这样每学一章就能立刻跑一遍书里的SQL。需要注意的是原书示例数据在个别版本里字段名有差异比如有的版本把商品价格字段命名为prod_price有的环境里却写成price。建表的时候先跑一遍官方附带的建表脚本确认没有问题再开始后面章节的练习能省掉后面很多莫名其妙的报错。还有个容易忽视的点SQL关键字的大小写。SQL本身不区分关键字大小写select和SELECT都能执行但是习惯上关键字用大写、表名和字段名保持原样查错时一眼就能区分。这个习惯建议从一开始就养成后面写复杂查询的时候会受益。1.2 SELECT语句的骨架与执行顺序认知第2章讲的是最基础的SELECT检索语法很简单SELECT prod_name, prod_price FROM Products;这里原书强调的是“你要查什么列就列出什么列用*虽然方便但要慎重”。这一点我强烈认同。用*取全部列在练习阶段没什么问题但是到了真实业务里一张宽表动辄三四十个字段用*取数会白白增加网络传输量和内存消耗而且当你只需要其中两三个字段时代码的可读性也会变得很差。更重要的其实是理解SQL的执行顺序。SELECT的书写顺序和逻辑执行顺序是不同的这个认知越早建立越好逻辑执行顺序是先找FROM对应的表再通过WHERE过滤接着GROUP BY分组再到HAVING过滤分组然后SELECT投影输出最后ORDER BY排序。很多新手不理解为什么WHERE里不能直接用SELECT子句中定义的别名就是因为执行顺序中WHERE发生在SELECT之前别名还没生成。原书第2章虽然没有系统讲执行顺序但从第4章开始你会在WHERE、GROUP BY、ORDER BY这些子句里反复碰壁提前理解执行顺序能把很多问题消灭在萌芽状态。2. 排序与过滤管理查询结果的两大基本功2.1 ORDER BY的排序逻辑与常见误区第3章讲排序核心就是ORDER BY子句。这里有一个原书明确提醒、但很多人还是会犯的错ORDER BY一定要放在WHERE子句之后顺序反了会直接报错。这背后其实就是上面说的执行顺序先过滤再排序语序上自然也是WHERE在前、ORDER BY在后。排序方向上DESC降序只作用于紧跟它的那一列不是作用于整个ORDER BY。比如SELECT prod_id, prod_price, prod_name FROM Products ORDER BY prod_price DESC, prod_name;这个语句的意思是先按价格降序价格相同的情况下再按商品名升序。你如果在心里默认所有列都降序那就错了。多列排序的场景在“先按分类再按时间”这种需求里非常常见值得多写几遍记住。还有一个坑字符型字段的排序结果取决于数据库的排序规则。有的数据库按二进制排序大写字母排在小写字母前面有的按不区分大小写的规则排序。所以当你发现排序结果和预期不一致先检查是不是大小写和排序规则的问题不要再Syntax上找半天。2.2 WHERE过滤与NULL的特殊身份第4章进入WHERE过滤这一章几乎是初学者分水岭因为NULL开始登场了。原书里明确写了一句WHERE prod_price IS NULL这种写法只能用来判断NULL不能用 NULL因为任何与NULL进行的比较运算结果都是未知unknown不会返回TRUE。我见过不少人在筛选空值的时候写WHERE 字段 NULL结果一条记录都查不出来还以为是数据问题。实际上你只要记住一句话判断NULL只能用IS NULL或者IS NOT NULL不要用任何比较运算符。另外NULL还会影响不等判断。比如要查询“价格不等于10”的商品你会写SELECT prod_name FROM Products WHERE prod_price 10;但如果表中存在价格字段为NULL的商品那么价格是NULL的商品不会被这条语句查出来。NULL表示“未知”数据库不会把未知当成“不等于10”来对待。这是一个非常现实的业务坑统计缺货商品、空值记录时如果过滤条件只写了不等判断遗漏掉NULL记录报表结果就会偏少。这类问题在原书最后几章的习题里体现得特别明显我建议你把NULL相关题目反复做两遍。2.3 AND、OR、IN、NOT的组合顺序第5章讲高级过滤核心就是操作符的组合。AND和OR组合时AND的优先级高于OR。原书举例的场景是查“价格10元及以上且由指定厂商供货或者由另一个厂商供货”的记录如果你不加括号结果会和预期相差很大。正确的写法是用括号把OR条件包起来写成SELECT prod_name, prod_price FROM Products WHERE (vend_id DLL01 OR vend_id BRS01) AND prod_price 10;这个习惯非常重要。不仅是为了逻辑正确更是为了可读性。我自己看别人写的SQL时最怕看到一长串用AND、OR串起来、又没有任何括号的WHERE条件每次都得拿笔在纸上画优先级。哪怕你的逻辑恰好不需要括号我也建议给每个组合条件加上括号这是在帮未来的自己省时间。IN操作符在这里值得单独说一下。IN的底层逻辑其实就是一组OR但它比一长串OR更好读而且执行计划在某些数据库里对IN的优化更好。NOT则常常和IN搭配写成NOT IN此时同样要注意NULL问题。3. 通配符、计算字段与函数让查询有表达力3.1 LIKE通配符的速度代价第6章讲通配符过滤也就是LIKE搭配%和_。这是写报表、做模糊搜索时经常用的功能但原书也明确提示了性能代价通配符搜索无法利用索引全表扫描几乎不可避免查询速度会随着表数据量增长急剧变差。我实际在工作中发现很多业务方的模糊搜索需求并不真的需要LIKE %关键词%这种前后都带百分号的写法。比如搜索“商品名以某个前缀开头”的需求用LIKE 前缀%在很多数据库里还能利用索引前缀匹配而LIKE %关键词%这种中间匹配才真的让索引彻底失效。_通配符匹配单个字符经常被忽略但它在匹配固定长度编码比如订单号、电话号码的时候特别好用。需要注意%可以匹配零个字符而_必须匹配一个字符这一点原书有提到但是做题时如果没注意还是容易错建议针对性练习几道。3.2 计算字段与别名数据库里做加工第7章是计算字段涉及拼接字段、算术运算和别名。这里最有用的概念是能在数据库里做的计算尽量不要拿到应用层再做。比如订单表里有单价和数量需要算总价直接在SQL里写prod_price * quantity返回计算好的结果既省流量又省应用层的代码逻辑。拼接字段在不同数据库里的语法不一样原书用的是RTRIM加上||操作符。这个细节恰好引出一个通用原则SQL标准虽然存在但不同数据库在字符串拼接、取子串、日期处理这些函数上都有各自的方言。你在某一种数据库里学会的写法换到另一种未必能跑通。所以实际写代码时要养成查阅当前数据库官方文档的习惯不要默认所有函数都一样。别名用AS关键字可以给计算字段起一个可读的名字。原书提示过别名有时候也会用在ORDER BY里。这一点很实用因为如果你在SELECT里写了一个复杂的计算表达式后面ORDER BY想按这个结果排序直接引用别名比重复写一遍整个表达式要清晰得多。3.3 数据处理函数可移植性陷阱第8章讲函数包括文本处理、日期处理、数值处理。这一章原书花了大量篇幅对比不同DBMS的差异核心观点就是SQL函数可移植性很差。我读这一章时的感触特别深。以前在一个项目里写过一个用年份截取来做统计的查询在一种数据库下跑得飞快迁移到另一种数据库直接报错报错原因就是日期函数的语法不兼容。从那以后我在涉及跨数据库迁移或者多环境兼容的项目里都会特别留意把函数用法抽出来做一个统一封装而不是在业务SQL里散落一地对函数的不同调用方式。日期处理函数尤其需要注意因为不同数据库对日期格式的默认解析规则不一样有的认为01-02-2024是1月2日有的认为是2月1日。原书在第8章里对这些差异做了清晰的表格对比强烈建议把那张表拍下来存着写代码之前先翻一翻。4. 汇总与分组从明细数据到统计结果4.1 聚合函数与NULL的again第9章讲汇总数据涉及COUNT、SUM、AVG、MAX、MIN。这一章内容不多但细节极其容易出错。第一个坑是COUNT(*)和COUNT(列名)的区别。COUNT(*)统计的是表的行数不管这行里有多少个NULL而COUNT(列名)只统计该列非NULL的个数。如果某列大量为空你用COUNT(列名)做统计结果会明显偏小。这个区别在统计用户订单数、有效联系方式数量这些场景中非常关键。第二个坑是AVG会忽略NULL行。计算平均价时如果某商品价格字段是NULL它不会进分母。这在数据质量不高的表里会导致平均值偏高或偏低取决于NULL分布在哪个量级。原书强调了AVG忽略NULL但没展开说业务影响我补一句做数据报表之前先检查关键数值字段的NULL比例否则很容易做出误导性的平均值。DISTINCT关键字可以配合聚合函数使用比如COUNT(DISTINCT 列名)统计非重复值个数。这个功能在做去重统计时经常用但要注意它也会忽略NULL而且COUNT(DISTINCT 多列)的行为在不同数据库里不一致使用时需要验证。4.2 GROUP BY的分组逻辑与易错点第10章进入分组GROUP BY是SQL里语义理解成本比较高的子句。原书给出了一个关键规则GROUP BY可以按多个列分组分组后SELECT里出现的列除了聚合函数计算列之外都必须出现在GROUP BY里。这条规则理解起来容易做错也容易。尤其在你习惯用SELECT *的时候一旦加上GROUP BY很容易报错因为*展开后的所有列不可能都放进GROUP BY。我当时练这一章时狠狠改过几次习惯SELECT里先明确列出需要的组列和聚合列而不是图省事写*。还有一个容易混淆的点GROUP BY和ORDER BY的位置关系。GROUP BY在WHERE之后、ORDER BY之前。执行顺序也是先过滤再分组最后排序。如果你在分组之后想按照聚合结果排序比如按销售额降序展示可以写成SELECT vend_id, SUM(quantity) AS total FROM OrderItems GROUP BY vend_id ORDER BY total DESC;这里ORDER BY用的是别名total完全没问题因为排序发生在SELECT之后。4.3 HAVING与WHERE的本质区别第10章的另一个重头戏是HAVING。原书给出的提示非常到位WHERE在数据分组之前进行过滤HAVING在数据分组之后进行过滤。这句话我在实际写SQL时反复体会。比如要筛选“订单数量2的客户”如果先按客户分组、再统计订单数那过滤条件肯定放在HAVING里因为在分组之前你根本不知道每个客户有多少订单。反过来如果需求是“只统计2024年的订单”那WHERE先过滤年份就能减少分组的行数性能更好。我曾经犯过一个错误在WHERE里试图使用聚合字段做条件判断类似WHERE COUNT(*) 2这种直接报错。后来才意识到聚合函数必须在分组之后才能生效所以这类条件只能放到HAVING。现在写SQL遇到“统计类条件”我第一反应就是它属于HAVING这能省下不少编译报错的时间。需要注意HAVING虽然可以和WHERE共存但书写顺序不能反永远是WHERE在前、HAVING在后对应执行顺序里的先过滤行、再分组、再过滤组。5. 子查询与联结多表查询的核心玩法5.1 子查询的三种典型场景第11章讲子查询也就是嵌套在另一个查询里的查询。原书给了三个典型场景WHERE子查询、计算字段子查询、作为派生表虽然这章讲得比较浅。实际业务里我也就主要用这三种。WHERE子查询最常见典型形态是“先查出一个集合再作为外层IN的条件”。比如先查出有订单的客户编号再根据这个编号集合去客户表里取信息。这里有一个经验一开始写子查询时可以分两步走先单独运行内层查询确认返回的结果集是想要的再把它嵌到外层这样排错效率高很多。子查询作为计算字段使用也是个重要场景。比如要查询每个客户的订单总数可以在SELECT里放一个关联子查询SELECT cust_name, (SELECT COUNT(*) FROM Orders WHERE Orders.cust_id Customers.cust_id) AS order_count FROM Customers;这种写法很灵活但要注意性能。关联子查询是对外层每一行都执行一次内层查询外层数据量大时开销不小。原书没有展开讲性能问题但我建议在大数据量场景尽量先考虑用联结或者窗口函数来替代。5.2 联结类型对比与JOIN语法核心第12章和第13章是全书的重中之重讲联结表。刚开始学联结时容易被各种JOIN术语搞晕但只要你抓住一个本质联结就是把两张表按某个关联条件“拼”成一张大表供查询使用。最常见的INNER JOIN只返回两表中关联字段匹配成功的行。LEFT OUTER JOIN返回左表的全部行右表没有匹配的用NULL填充RIGHT OUTER JOIN反过来FULL OUTER JOIN两边都全保留没匹配的用NULL填充。这里有一个重要的通用写法建议使用显式JOIN语法连接条件写在ON子句中过滤条件写在WHERE中。原书提到过隐式联结用逗号分隔多张表再加WHERE这是老式写法也能跑但可读性差而且容易漏掉关联条件形成笛卡尔积而不自知。我现在写SQL基本只用显式JOIN每个JOIN都写清楚ON条件一眼能看出表之间的关联关系。多表联结时表名和字段名最好都带上限定前缀例如Orders.cust_id不然多个表里出现同名字段时SQL不会报错但结果很可能出错而且排查成本极高。这个习惯真的是在项目里吃了亏才养成的。5.3 自联结与笛卡尔积的防范第13章还有一个容易被人忽略的考点自联结。自联结就是把一张表当成两张不同的表来使用。比如查询某商品同一厂商的其他商品可以把产品表分别命名为p1和p2然后按厂商ID关联。自联结的写法刚开始看起来有点绕但它比子查询在某些场景下性能更好这点原书也提过。我自己做练习时会把同一道题分别用子查询和自联结各写一遍对比两者的执行计划这是理解两种方案差异最快的方式。另外当我们想要改造成多表SQL踩坑时自联结是练手的好场景。笛卡尔积是另一个必须提的坑。所谓笛卡尔积就是两张表联结时没有写ON条件导致每一行都和另一张表的每一行组合结果行数等于两张表行数的乘积。如果一张表5万行另一张表8万行一次失误的联结就可能生成40亿行直接把数据库拖垮。原书专门用一节提醒不要忘了WHERE或者ON条件。我在写多表查询时有个习惯写完JOIN之后先看一眼返回行数是否合理如果异常膨胀第一个怀疑的就是关联条件漏了。6. 组合查询与插入数据查询的补完与写入初探6.1 UNION的规则与去重语义第14章讲组合查询用UNION把多条SELECT语句的结果合并成一个结果集。原书列出了几条规则每条SELECT必须包含相同的列、表达式或聚合函数列数据类型必须兼容UNION默认去重如果需要保留所有行用UNION ALL。我在读这一章的时候对UNION的性能有了新的理解UNION默认要额外做一次去重操作代价不小如果业务上能确定两条查询结果不会重复或者重复了也无所谓直接用UNION ALL会快很多。这个优化点在很多报表SQL里非常有用。另外UNION里的ORDER BY只能在整个语句的末尾出现一次不能分别对每一条SELECT排序。这一点原书用一小段提了但容易忽略。我建议所有对UNION结果的排序都只在最后一条SELECT后面加ORDER BY。还有一个常见需求想要合并多个表的结构相同但数据不同的部分比如不同月份的销售表合并统计UNION ALL就是标准答案。会用UNION之后你会发现很多“拆开查再拼接”的业务逻辑可以直接在SQL里完成。6.2 INSERT的两种写法与自增主键第15章从查询转到插入数据讲的是INSERT。原书介绍了两种语法一种是明确列出列名另一种是不带列名、按表结构顺序给值。我想特别强调永远使用明确列出列名的写法。原因有三点。第一表结构一旦变更新增列、调整顺序不带列名的INSERT就会错位甚至报错而带列名的INSERT基本不受影响。第二带列名的INSERT可读性更高后人看到每一列对应的值很清楚。第三如果你想给部分列插入值比如自增主键不填只有带列名的写法才能做到。实际开发里插入操作经常要配合自增主键。比如订单表和订单明细表需要在插入订单后拿到新生成的订单ID再插入明细。不同数据库获取刚插入的自增ID方式不同有的用LAST_INSERT_ID()有的用RETURNING原书没有展开这一块但这是真实项目里几乎必遇的问题建议你学到这一章时顺手查一下当前数据库的对应写法。6.3 从插入到数据完整性的延伸思考第15章虽然只讲了INSERT但原书也隐隐提到“插入数据要符合表结构定义”这一层其实牵出了数据完整性的概念。外键约束、非空约束、唯一约束都会直接影响INSERT能否成功。我见过很多初学者在练习INSERT时报错原因是往一个设置了NOT NULL的字段插入NULL或者往有唯一约束的字段插入了重复值。这时候不要急着改表结构先仔细看报错信息往往能直接定位到约束条件。读到第15章意味着你已经从纯查询跨到了写操作。写操作比查询更需要谨慎因为数据一旦写入再回头修正的成本很高。如果你是在练习环境里折腾那无所谓但如果是真实业务库INSERT之前最好先确认数据正确性必要时开事务执行出问题还能回滚。这一块后半本书会有更系统的讲解第15章作为开始先把INSERT本身写顺再逐步探索事务与约束这些外围能力。写到这里第1~15章的核心内容就过了一遍。我个人最深的体会是SQL看起来是语法问题实际上是逻辑问题。每一段查询背后都有执行顺序、NULL语义、数据类型这些隐藏约束原书把这些基础点讲得清清楚楚但真正内化还得靠动手。建议你学完每一章后把书里的例子改成自己的业务假设跑一遍比如把“商品价格大于10”改成“订单金额大于500”多折腾几次对语法的理解完全不一样。这本书后半部分还有事务处理、游标、触发器、存储过程这些内容等我把第16章之后的内容整理完再单独写一篇下册笔记。先祝各位把基础打好后续进阶才不虚。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑