连接把两张表拼在一起
关系型数据库有意把数据拆分到多张表中:客户在一张表,订单在另一张表,商品在第三张表。这样每条事实只存放在一个地方。但当你想回答一个实际的问题(“哪些客户订了什么?”)时,就需要把这些碎片重新拼起来。这正是连接的作用。
INNER JOIN 是最常用的连接。只要条件匹配,它就把两张表中的行配成一对,其余的全部丢弃。
三个客户,三个订单,但 Chen 没有订单,所以 Chen 不会出现。这就是“内”连接的含义:只有匹配的行才会留下。
思维模型:先配对,再过滤
可以这样理解 INNER JOIN:取第一张表的每一行,与第二张表的每一行逐一比对,只有当 ON 条件为真时才保留这一对。从概念上讲,它是先做一个巨大的笛卡尔积,再进行过滤。SQLite 实际上并不是这样执行的(能用索引时它会用索引),但用这个模型来预测输出结果是正确的。
这里有几个值得养成的习惯:
- 如果一张表要被提到不止一次,就给它起别名(
customers AS c)。这样能减少干扰。 - 当两张表都可能有某一列时,给列名加上限定(
c.name、o.total)。 ON o.customer_id = c.id中的顺序无关紧要:c.id = o.customer_id效果一样。
INNER JOIN 与 JOIN
在 SQLite(以及标准 SQL)中,单独写 JOIN 就表示 INNER JOIN。INNER 关键字是可选的。
两种写法产生相同的执行计划和相同的行。在混用多种连接的代码中,写全 INNER JOIN 能稍微提高可读性:和几行之后的 LEFT JOIN 放在一起时,意图一目了然。
ON 与 USING
当连接列在两张表中同名时,USING (column) 比 ON a.col = b.col 更简短:
USING (customer_id) 做了两件事:按 customer_id 相等进行匹配,并把这一列合并,让它在结果中只出现一次。当两边确实使用同一个列名时就用它。当列名不同(orders.customer_id = customers.id),或者条件不只是一个等式时,继续使用 ON。
连接三张表
再添加更多的 JOIN ... ON ... 子句,就能把连接串起来。每一个子句都把当前的结果和另一张表关联起来。
从上往下读:客户连接到订单,订单连接到订单项。输出中的每一行代表一个“客户、订单、订单项”的组合。链条上任何一处缺少匹配的行都会被丢弃:这就是内连接规则在每一步的应用。
用 WHERE 过滤
ON 说明如何配对行,WHERE 过滤配对后的结果。具体到内连接,把额外的条件放在 ON 还是 WHERE 中,得到的行是一样的;但惯例是把连接条件放在 ON 中,把行过滤条件放在 WHERE 中。
它读起来就是:“连接客户和订单,然后只保留订单金额超过 20 的英国客户。”两种职责,两个子句,将来的你会感谢现在的自己。(等你开始写 LEFT JOIN 时,ON/WHERE 的区别就不再只是写法问题了,不过那是下一页的内容。)
ON 中的多个条件
ON 可以包含任意布尔表达式,而不只是一个等式。当关系涉及不止一列,或者你想在连接时就过滤右表时,这很有用。
已取消的订单消失了,因为第二个条件不成立。对于内连接,你也可以写成 WHERE o.status = 'paid',结果相同。ON 的写法让“什么算匹配”的逻辑紧挨着连接本身。
常见的坑
有几件事经常让人出错:
- 忘了写
ON子句。 在 SQLite 中,没有ON的FROM a INNER JOIN b是语法错误。(只用逗号的FROM a, b能通过编译,会得到一个交叉连接,而这几乎从来都不是你想要的。) - 意料之外的重复。 如果一个客户有三个订单,这个客户的名字就会在结果中出现三次。这是正确的连接行为,不是 bug。如果你想每个客户只占一行,就用
GROUP BY做聚合。 - 缺失的行。 如果某个客户本该出现却没有出现,说明连接条件没有匹配上:检查连接列中是否有
NULL,或者改用LEFT JOIN。 - 列名有歧义。
SELECT id FROM customers JOIN orders ON ...会报错,因为两张表都有id。给它加上限定:c.id或o.id。
下一篇:LEFT JOIN
当缺少匹配意味着“跳过这一行”时,INNER JOIN 非常好用。但有时你想列出每一个客户,即使是没有订单的客户,并用 NULL 代替缺失的数据。这就是接下来要讲的 LEFT JOIN。
常见问题
SQLite 中 INNER JOIN 有什么作用?
INNER JOIN 根据 ON 条件,返回在两张表中都有匹配的行。任何一边没有匹配的行都会被丢弃。它是默认的连接方式:在 SQLite 中,JOIN 和 INNER JOIN 的意思相同。
SQLite 中 INNER JOIN 和 LEFT JOIN 有什么区别?
INNER JOIN 只保留匹配的行。LEFT JOIN 保留左表的每一行,没有匹配时右侧用 NULL 填充。当缺少匹配意味着“跳过这一行”时用 INNER JOIN,当缺少匹配意味着“照样显示”时用 LEFT JOIN。
SQLite 中可以 INNER JOIN 三张表吗?
可以:再接一个 JOIN ... ON ... 子句即可。每次连接都把当前的结果和一张新表关联起来。没有硬性的数量限制,但超过四五张表后可读性会迅速下降,这时 CTE 往往会有帮助。
什么时候应该用 USING 而不是 ON?
当连接列在两张表中同名时,USING (column) 是一种简写。它更简洁,并且会在输出中把重复的列合并成一列。只要列名不同,或者需要更复杂的条件,就用 ON。