自连接就是带别名的连接
自连接并没有什么特别之处。它就是一个普通的 JOIN,只是两边恰好是同一张表。关键在于 SQLite 需要分辨这两份表,所以你要给每一份起一个别名。
只要表中的一行引用了同一张表里的另一行,你就会用到它。经典的例子是一张 employees 表,每一行都有一个 manager_id 指向另一名员工:
Ada 没有经理。Boris 和 Cleo 向 Ada 汇报。Diego 和 Esme 向 Boris 汇报。这种关系完全存在于一张表内部,而这正是自连接派上用场的地方。
基本结构
要把每名员工和他经理的名字配对,就把 employees 和它自己连接。一份扮演“员工”,另一份扮演“经理”:
把它读成两张恰好共用存储的表。e 是员工行,m 是经理行。连接条件 e.manager_id = m.id 把它们对齐:对每名员工,在 m 中找出 id 等于该员工 manager_id 的那一行。
注意 Ada 没有出现。她的 manager_id 是 NULL,而 INNER JOIN 会丢掉没有匹配的行。
保留没有匹配的行:LEFT JOIN
如果你希望结果中包含所有人,包括没有经理的人,就换成 LEFT JOIN:
现在 Ada 出现了,经理一列是 NULL。自连接的机制完全一样,只是连接类型做了 LEFT JOIN 一贯会做的事:保留左边的每一行,没有匹配的地方留空。
展示人员列表时,通常要的就是这种写法。“没有经理”本身就是信息;把这一行丢掉可不是。
别名不是可选的
试试不加别名做连接,SQLite 根本不知道你要什么:
SELECT name, manager_id FROM employees JOIN employees ON manager_id = id;
-- Error: ambiguous column name: name
每一列都出现了两次,两份表各一次,SQLite 没法选择。别名给每个实例起了自己的名字,从而解决这个问题。选择能描述这一行所扮演角色的别名,而不是描述表的别名:
- 员工与经理用
e和m。 - 层级结构用
parent和child。 - 比较任意两两组合时用
a和b。
自连接读起来清晰,全靠别名。
在一张表中找出配对
自连接不只用于层级结构。只要你想比较同一张表中的行,这个模式就适用。下面是一个商品列表,我们想找出所有价格相同的商品对:
有两点要注意。第一,a.price = b.price 才是真正的匹配条件。第二,a.id < b.id 让查询不会把每一对返回两次(一次是 (Mug, Notebook),另一次是 (Notebook, Mug)),也不会把每一行和它自己配对。这个 < 的技巧值得记住:每次列出配对时都会用到它。
往上走两层
一次自连接只能处理层级中的一跳。想知道每名员工经理的经理?那就连接三次:
每多一个别名,就代表在树中再往上一层。两三跳时这样做没问题,但很快就撑不住了:你得在写查询时就知道层级有多深,并为每一层加一个连接。递归 CTE 正是为了突破这堵墙而发明的。
什么时候不该用自连接
当你需要在结果中显示关系两边的列时,自连接是正确的工具。如果你只需要筛选,比如找出所有经理是 Ada 的员工,子查询通常更好读:
不用摆弄别名,意图一目了然。经验法则:你希望输出中包含两行的数据吗?用自连接。你只需要一个用来比较的值吗?用子查询。
对于深度任意的层级结构(组织架构图、文件树、楼层式评论),两种模式都无法扩展。那是递归 CTE 的领域。
下一步:子查询
自连接和子查询解决的问题有重叠,知道该用哪一个,能让你以后少对着 SQL 皱眉头。下一页会深入讲解子查询:标量子查询、相关子查询和 IN 形式,以及它们各自的用武之地。
常见问题
SQLite 中的自连接是什么?
自连接就是一个普通的 JOIN,只不过是一张表和它自己连接。你给同一张表起两个不同的别名,让 SQLite 把它们当作两个独立的行来源,再用一列把一行和另一行关联起来,最常见的是父子关系,比如员工与经理。
为什么自连接需要别名?
没有别名,你写一个列名时,SQLite 无法判断你指的是哪一份表。给每个实例起一个自己的别名(比如员工用 e,经理用 m),就能毫无歧义地写出 e.manager_id = m.id。别名不是可选的:没有它们,查询根本无法解析。
什么时候用自连接,什么时候用子查询?
当你希望结果中同时出现两行的列时,用自连接,比如在同一行里显示员工的名字和经理的名字。当你只需要筛选或查找一个值时,用子查询。对于嵌套很深的层级结构,两者都不太合适;递归 CTE 才是正确的工具。