数据分表存是为了不重复,联结是为了把它们拼回来查。这一章讲清一件事:联结条件写在哪, 决定你拿到 13 行还是 78 行。 Demo 里不写条件 6 乘 13 得 78 行,补上条件立刻回到 13 行; 三个表两个条件串成一条链,20005 那张单卖的是哪家的什么货一眼看清; 最后用行数自证:13 对 13,没丢没炸。
上一章的子查询是「一次性的中间表」, 联结是把表直接摆在一起查——同样的问题,联结通常更快,也更像 SQL 的本意。 这是全书的分水岭:前面所有查询都只碰一张表,从这一章开始多表协作,第 16 章的高级联结、 第 22 章的视图都建立在这里。
一、为什么要拆表:同一份信息只存一处
ACME 的地址只在 vendors 里存一份,products 里只留一个 vend_id。 这样改地址只要改一行,不会出现「六个产品五个地址」的乱象。 这就是关系型数据库的核心设计:拆表消除重复。 代价是取数时要拼回去——拼的动作就叫联结(join)。 拆表的规矩在教材第 1 章就埋下了:一张表只讲一件事, 跨表用主键与外键挂钩(products.vend_id 指向 vendors.vend_id)。
二、笛卡尔积:少写一个条件的后果
FROM vendors, products 不带条件,结果是「每对都配一次」:
6 家供应商乘 13 个产品 = 78 行。这叫笛卡尔积。
它不是错误结果,是「没有约束的结果」——数据库老老实实把每种组合都给你了。
但对业务来说这就是事故:报表行数暴涨几十倍,汇总全错。
排查口诀:行数突然变多,先找联结条件。 N 张表要 N-1 个条件,少一个就退化成部分笛卡尔积。
三、创建联结:等值联结
把条件补上就是等值联结:WHERE vendors.vend_id = products.vend_id,78 行立刻变回 13 行。
两个表有同名列时必须写表名前缀(vendors.vend_id),否则数据库不知道你说哪张表的。
这和第 14 章那条规矩是同一条。
教材把这种写法叫「等值联结」,
也叫内部联结——只保留两边都能对上的行。 对不上的行直接不出现(第 16 章的外联结才保留)。
四、INNER JOIN 写法与多表联结
换个写法:FROM vendors INNER JOIN products ON vendors.vend_id = products.vend_id。
结果完全一样,只是把「表」和「条件」分成两块写。INNER 可以省略。
两种写法教材都给了:老写法短,新写法把「怎么连」和「筛什么」分开了,表一多就看出好处。
注意 JOIN 写法下条件写 ON 里,别往 WHERE 塞——虽然能跑,
但换成外联结时语义会变(第 16 章细讲)。
多表联结就是接链子:orderitems 认产品、产品认供应商,三个表两个条件。
20005 那张单卖的是哪家的什么货,一眼看清。
写完一定要验算行数:products 有 13 行、每个 vend_id 都能在 vendors 里找到,
联结后就该是 13 行。13 对 13,说明没丢也没炸。
五、小结
教材小结: 联结用 WHERE 或 JOIN…ON 把多张表按主外键拼起来,不写条件是笛卡尔积,N 张表要 N-1 个条件。 本课补三条:一是行数突然变多先查联结条件;二是同名列必须加表名前缀; 三是写完用行数自证,这是最便宜的体检。
六、动手 Demo:78 行怎么变回 13 行
Demo 六段:先看拆表的好处,再故意不写条件看 78 行,然后补上条件、换 JOIN 写法、串三个表, 最后用行数验算。 重点看行数的三次变化:78、13、4——每一次都能对上账,说明你读懂了自己的 SQL。
import sqlite3
con = sqlite3.connect(":memory:")
# 同一份供应商信息只存一处,产品表只留 vend_id
con.execute("SELECT vend_id, vend_name, vend_city FROM vendors WHERE vend_id = 1003")
con.execute("SELECT prod_id, prod_name, vend_id FROM products WHERE vend_id = 1003")
# 不写联结条件:6 乘 13 得 78 行,这是事故不是结果
con.execute("SELECT COUNT(*) FROM vendors, products")
# 等值联结:把条件补上,回到 13 行
con.execute("SELECT vend_name, prod_name, prod_price FROM vendors, products "
"WHERE vendors.vend_id = products.vend_id ORDER BY prod_name")
# 另一种写法:JOIN ... ON
con.execute("SELECT vend_name, prod_name, prod_price FROM vendors "
"INNER JOIN products ON vendors.vend_id = products.vend_id")
# 三个表两个条件:20005 这张单卖的是哪家的什么货
con.execute("SELECT vend_name, prod_name, quantity, item_price "
"FROM orderitems, products, vendors "
"WHERE products.prod_id = orderitems.prod_id "
"AND vendors.vend_id = products.vend_id AND order_num = 20005")
# 验算:联结后的行数应该等于产品表行数
con.execute("SELECT COUNT(*) AS n FROM vendors INNER JOIN products "
"ON vendors.vend_id = products.vend_id")
实跑输出:
--------------------------------------------------------------------
第十五课:联结表
--------------------------------------------------------------------
【一】数据为什么分表:同一份信息只存一处
SQL> SELECT vend_id, vend_name, vend_city, vend_country FROM vendors WHERE vend_id = 1003
vend_id vend_name vend_city vend_country
-------------------------------------------------------
1003 ACME Los Angeles USA
(1 行)
SQL> SELECT prod_id, prod_name, vend_id FROM products WHERE vend_id = 1003
prod_id prod_name vend_id
-----------------------------------------
DTNTR Detonator 1003
FB Bird seed 1003
FC Carrots 1003
SLING Sling 1003
TNT1 TNT (1 stick) 1003
TNT2 TNT (5 sticks) 1003
(6 行)
**ACME 的地址只在 vendors 里存了一份**,products 里只留一个 vend_id。
**这样改了地址只要改一行**,不会出现「六个产品五个地址」的乱象。
**代价是取数时要拼回去**——这就是联结要干的事。
【二】不写联结条件会怎样:笛卡尔积
`SELECT COUNT(*) FROM vendors, products` 给 **78 行**:
**6 家供应商乘 13 个产品,每对都配一次。** 这不是结果,
是事故——**少写一个 WHERE,报表就多出几十倍的行。**
【三】等值联结:把条件补上
SQL> SELECT vend_name, prod_name, prod_price FROM vendors,
products WHERE vendors.vend_id = products.vend_id ORDER BY vend_name, prod_name
vend_name prod_name prod_price
----------------------------------------------
ACME Bird seed 10.00
ACME Carrots 2.50
ACME Detonator 13.00
ACME Sling 4.49
ACME TNT (1 stick) 2.50
ACME TNT (5 sticks) 10.00
Anvils R Us .5 ton anvil 5.99
Anvils R Us 1 ton anvil 9.99
Anvils R Us 2 ton anvil 14.99
Anvils R Us Safe 50.00
Jet Set JetPack 2000 55.00
LT Supplies Fuses 3.42
LT Supplies Oil can 8.99
(13 行)
**13 行,一行不多。** 条件 `vendors.vend_id = products.vend_id` 就是联结条件。
**两个表有同名列时必须写表名前缀**,否则数据库不知道你说的是哪张表的。
【四】另一种写法:INNER JOIN ... ON
SQL> SELECT vend_name, prod_name,
prod_price FROM vendors INNER JOIN products ON vendors.vend_id = products.vend_id ORDER BY prod_name
vend_name prod_name prod_price
----------------------------------------------
Anvils R Us .5 ton anvil 5.99
Anvils R Us 1 ton anvil 9.99
Anvils R Us 2 ton anvil 14.99
ACME Bird seed 10.00
ACME Carrots 2.50
ACME Detonator 13.00
LT Supplies Fuses 3.42
Jet Set JetPack 2000 55.00
LT Supplies Oil can 8.99
Anvils R Us Safe 50.00
ACME Sling 4.49
ACME TNT (1 stick) 2.50
ACME TNT (5 sticks) 10.00
(13 行)
**结果一模一样,只是把「表」和「条件」分开写了。**
**ON 就是联结条件,INNER JOIN 的 INNER 可以省略**,写 JOIN 也一样。
**两种写法都合法,教材两种都给**:老写法简单,新写法把条件和过滤分开,更清楚。
**注意:JOIN 写法下条件写在 ON 里,别再往 WHERE 塞**,虽然塞了也能跑,
但语义会变(外联结时尤其致命,第 16 章讲)。
【五】联结多张表:一条链串三个
SQL> SELECT vend_name, prod_name, quantity, item_price FROM orderitems, products,
vendors WHERE products.prod_id = orderitems.prod_id AND vendors.vend_id = products.vend_id AND order_num = 20005
vend_name prod_name quantity item_price
-----------------------------------------------------------
Anvils R Us .5 ton anvil 10 5.99
Anvils R Us 1 ton anvil 3 9.99
ACME TNT (5 sticks) 5 10.00
ACME Bird seed 1 10.00
(4 行)
**三个表两个条件,一条链**:orderitems 认产品、产品认供应商,
**20005 那张单卖的是哪家的什么货,一眼看清。**
**N 张表要 N-1 个联结条件**,少一个就退化成笛卡尔积的一部分。
【六】验算:用行数自证
联结写完怎么知道对不对?
**数一下行数。** products 里有 13 行、每个 vend_id 都能在 vendors 里找到,联结后就该是 13 行。
SQL> SELECT COUNT(*) AS n FROM vendors INNER JOIN products ON vendors.vend_id = products.vend_id
n
---
13
(1 行)
**13 对 13,说明没丢行也没炸行。** **要是联结后行数变多,先查联结条件是不是唯一**——
那就是重复匹配的信号。
第一段拆表。 ACME 的地址在一行里,六个产品各只带一个 vend_id。 第二段笛卡尔积。 只管数行数:6 乘 13 得 78,这就是少写条件的代价。 第三段等值联结。 13 行,ACME 六个、Anvils R Us 四个、LT Supplies 两个、Jet Set 一个。 第四段 JOIN 写法。 结果和第三段逐行一致,只是写法换了。 第五段三个表。 20005 四行,每行都带上了供应商名——这就是「卖出的是谁的货」这个问题的答案。 第六段验算。 13 对 13,没丢行、没炸行。
30 秒实验
- 把
ORDER BY prod_name改成ORDER BY vend_name,13 行的顺序怎么变? - 把 20005 换成 20009,三个表的联结各出几行?
- 把联结条件删掉只留
order_num = 20005,行数会变成多少?先猜再跑。
常见误区
- 写 FROM a, b 却忘了 WHERE。得到的是笛卡尔积,行数是两表行数相乘。
- 同名列不加表名前缀。数据库不知道指哪张表,重名时报错,不重名时静默给错值。
- 以为 JOIN 写法下条件能随便放。写在 ON 和写在 WHERE 在外联结时结果不同。
自测三问(点开对答案)
1. 笛卡尔积是怎么来的?怎么避免? —— 多表查询没写联结条件,每行与另一表每行都配一次; 补上 `WHERE a.id = b.a_id` 或 `JOIN…ON` 即可。 2. `vendors, products` 加 `WHERE` 和 `INNER JOIN…ON` 有什么区别? —— 结果相同,只是写法不同:前者把条件写在 WHERE,后者写在 ON;表多时 JOIN 写法更清楚。 3. 为什么联结完要先数一下行数? —— 行数是最便宜的体检:等于主表行数说明没丢没炸;变多说明条件不唯一或漏了条件。内部联结只留两边都对上的行, 那对不上的呢? 下一章讲外联结、自联结、自然联结,还有表别名这件省字神器。
- ☐ (本章完,下一章 第16章 创建高级联结)