哈喽,小伙伴们,我是老K。上期我们聊了单表查询,大家应该已经学会了怎么从一个表里筛选、排序、分组、聚合了吧?但实际开发中,数据往往不是孤零零待在一个表里的,而是分散在多个相互关联的表中。比如商品信息在一个表,商品分类在另一个表,订单信息又在别的表……这时候,如果我们想同时拿到商品和它对应的分类名称,光靠单表查询就搞不定了,必须得把多个表“连”起来查——这就是我们今天的主角:多表查询。
为什么要把数据分开存?
你可能会想:“干嘛要分开啊?全放一个表里不就行了?”
来,咱们举个例子:假如你做一个电商网站,商品表里除了商品ID、名称、价格,还要存分类信息。如果所有商品都放一张表,那“手机”这个分类名称就会在每个手机商品里重复出现成千上万次,不但浪费空间,而且哪天想把“手机”改成“智能手机”,你还得把几千条记录都改一遍,多麻烦!
所以,聪明的做法是把分类单独拎出来做成一张表(category),商品表(products)里只存一个分类ID,指向分类表的主键。这样既节省空间,又方便维护。这种分类ID就叫外键,它把两个表关联了起来。
简单记:主表(一方)提供主键,从表(多方)用外键引用它。
多表查询的本质
多表查询的本质就是:根据表之间的关联关系(通常是主外键),把多个表临时合并成一张大表,然后再从这个大表里查询数据。合并的过程叫连接(JOIN)。
下面我们用实际数据来演示。先准备两张表:商品表(products)和分类表(category),并插入一些数据(数据在文末附录,大家可以先创建)。
1. 连接查询
1.1 交叉连接(千万别乱用!)
如果我们直接把两个表放在一起查,会发生什么?
比如:

结果会返回两个表记录数的乘积:products有14条,category有4条,最终得到56条记录。这其实是把每件商品和每个分类都匹配了一遍,产生了大量毫无意义的数据。这种结果叫笛卡尔积,现在这几条数据看着不多,但是一旦表内的数据过多,它们相乘的积会是一个很恐怖的数,并且其中99%是没有的数据,工作中一定要避免!——除非你确实需要这种全组合,但绝大多数情况我们都需要加上关联条件。
1.2 内连接(INNER JOIN)
内连接就是取两个表的交集,只返回符合关联条件的数据。
比如我们要查询每个商品所属的分类信息,可以通过 category_id = id 关联起来。
隐式内连接(用 WHERE 写条件):

显式内连接(用 INNER JOIN … ON):

两种写法结果一样,推荐用显式,因为逻辑更清晰。
1.3 外连接(LEFT / RIGHT JOIN)
内连接只返回两边都匹配的数据,但有时候我们想把主表(比如分类表)的所有记录都显示出来,不管它下面有没有商品。这时候就需要外连接。
左外连接(LEFT OUTER JOIN):返回左表的全部记录,右表没有匹配的就补 NULL。
结果中只显示有分类的商品,那些没有分类的商品(比如我们故意插入的“百草味腰子”,category_id 为 NULL)不会出现。

这样,即使“家居”分类下没有商品(我们的数据里确实没有),它也会出现,只是商品字段为 NULL。
右外连接(RIGHT OUTER JOIN):和左外相反,返回右表的全部记录。上面例子如果改成右连接,就需要把 category 放在右边:

结果和左连接一样。实际工作中左连接用得更多,习惯把主表放在左边。
1.4 全外连接(MySQL 里用 UNION 模拟)
MySQL 不支持 FULL OUTER JOIN,但我们可以用 UNION 把左连接和右连接的结果合并起来,达到全外连接的效果。

这样就能得到所有商品和所有分类的大合集,哪边缺了就补 NULL。
2. 自连接
有时候,一张表里的记录之间有关系,比如省市区表:省有 pid 指向自己的 id(省级的 pid 为 0 或 NULL),市区的 pid 指向所属的上级。这种自己和自己连接就叫自连接。
举个例子,从 areas 表里查江苏省下面的所有城市:

自连接必须给表起别名(province 和 city),否则 SQL 分不清哪是哪。
自连接还有很多妙用,比如计算环比、累计等,大家可以慢慢探索。
3. 子查询
子查询就是在一个查询里再嵌套一个查询,把内层查询的结果作为外层查询的条件、数据源或字段。
3.1 作为条件
需求:查出价格高于平均价的商品。
子查询先算出平均价,外层查询再筛选。
3.2 作为数据源(临时表)
需求:查每个分类的商品平均价,并显示分类名称。
可以先在商品表里分组算出每个分类的平均价,得到一张临时表,然后再和分类表连接。
3.3 作为查询字段
需求:查询每个学生的成绩,并附带全校平均分。
这里子查询的结果作为一个字段出现在每一行中。
小结
-
多表关系:一对多(最常见),多对多用中间表转为一对多。
-
连接查询:内连接(交集),外连接(主表全保留),自连接(自己连自己)。
-
子查询:嵌套查询,可以充当条件、表、字段。
-
注意:写连接时一定要有关联条件,避免笛卡尔积。
多表查询是 SQL 进阶的关键,刚开始可能会觉得有点绕,但只要多动手练习,很快就会熟练。建议大家把今天讲的几种查询都敲一遍,特别是结合实际的业务需求,比如“每个分类下商品数量”、“最近一个月销量最好的商品”等等。下期我们聊聊事务和索引,让你的数据库操作更高效、更安全。有问题欢迎在评论区留言,老K看到就会回复!
附录:建表及数据


同学们也可以自己手动多练习练习,好啦,今天的分享就到这里。如果你觉得有用,记得点赞、收藏、转发给同样在学习数据库的小伙






