🍺学而不思则罔,思而不学则殆。——《论语》
大家好!我是只谈技术不剪发的 Tony 老师。
当我们在学习 SQL 连接查询(JOIN)的时候,大概率会看到类似下面的这种文氏图(Venn diagram)。很多人都使用文氏图解释连接查询的语法,但这是一种错误的方法,至少是不准确的方法。接下来给大家解释一下为什么,同时给出一个正确理解连接查询的方法。
如果你觉得文章有用,欢迎评论📝、点赞👍、推荐🎁
连接查询
SQL 连接查询可以同时获取多个表中的关联数据。例如,查看某个完整的订单数据时,可能需要从产品表、用户表、用户订单表、以及订单明细表中获取相关的信息。
SQL 中的连接查询包括:
- 内连接(INNER JOIN);
- 左外连接(LEFT OUTER JOIN);
- 右外连接(RIGHT OUTER JOIN);
- 交叉连接(CROSS JOIN);
- 自然连接(NATURAL JOIN);
- 半连接(Semi Join);
- 反连接(Anti Join);
- 自连接(Self Join)。
所有连接查询的基础都是交叉连接,也就是笛卡儿积(Cartesian Product)。交叉连接的示意图如下所示:
两个表的交叉连接相当于一个表的所有行和另一个表的所有行两两组合,结果的数量为两个表的行数相乘。如果第一个表有 100 行,第二个表有 200 行,它们的交叉连接将会产生 20000 行数据。
交叉连接显然无法使用文氏图进行描述。
对于其他连接类型,通常也无法使用文氏图进行准确描述,因为文氏图的作用是描述两个集合之间的关系,例如集合 A 包含集合 B,或者集合 A 和 集合 B 的公共元素。连接查询的含义则是另外一回事,例如以下内连接:
select d.dept_id,
e.dept_id,
dept_name,
e.emp_name,
e.sex
from employee e
join department d on e.dept_id = d.dept_id;
首先,employee 和 department 的元素并不相同。以上查询返回的是基于 dept_id 字段的关联数据,而不是两个表中的公共记录(元素)。
另外,文氏图返回的公共元素个数不会超过集合 A 和集合 B 两者中较少的元素个数。但是对于以上查询,employee 包含 25 条数据,department 包含 6 条数据,内连接返回的结果数量为 25,它们是交叉连接的子集。
内连接的示意图如下所示:
左外连接返回左表中所有的数据行;对于右表,如果没有匹配的数据,显示为空值。左外连接使用关键字LEFT OUTER JOIN表示,也可以简写成LEFT JOIN。 左外连接可以参考以下示意图(基于两个表的 id 进行等值连接):
右外连接返回右表中所有的数据行;对于左表,如果没有匹配的数据,显示为空值。右外连接使用关键字RIGHT OUTER JOIN表示,也可以简写成RIGHT JOIN; 右外连接可以参考以下示意图(基于两个表的 id 进行等值连接):
SQL 全外连接等效于左外连接加上右外连接,返回左表和右表中所有的数据行。全外连接使用关键字FULL OUTER JOIN表示,也可以简写成FULL JOIN。全外连接的示意图如下(基于两个表的 id 进行连接):
📝SQL 标准没有定义半连接和反连接查询的语法,而是通过子查询的方式实现,具体可以参考这篇文章。
集合运算
文氏图正确的用途是描述集合运算,包括:
- 交集(UNION、UNION ALL);
- 并集(INTERSECT);
- 差集(EXCEPT、MINUS)。
SQL 集合操作符要求参与运算的表或查询结果具有相同数量的列,以及对应列的类型必须匹配或兼容。
UNION操作符用于将两个查询结果合并成一个结果集,包含了第一个查询结果以及第二个查询结果中的数据。
其中,ALL 表示保留结果集中的重复记录;如果省略,表示将合并后的结果集进行去重。
INTERSECT 操作符用于返回两个查询结果中的共同部分,即同时出现在第一个查询结果和第二个查询结果中的数据。
EXCEPT 操作符用于返回出现在第一个查询结果中,但不在第二个查询结果中的数据。
我们以 UNION 为例:
SELECT first_name, last_name
FROM customer
UNION ALL
SELECT first_name, last_name
FROM staff;
以上查询返回了 customer 数据和 staff 数据的并集。对于该查询,我们也可以使用连接查询实现:
SELECT COALESCE(c.first_name, s.first_name) AS first_name, COALESCE(c.last_name, s.last_name) AS last_name
FROM customer c
FULL OUTER JOIN staff s
ON (c.first_name = s.first_name AND c.last_name = s.last_name);
对于以上连接查询,我们需要在连接条件中包含所有返回的字段。
除了 UNION 之外,INTERSECT 和 EXCEPT 也可以使用等价的连接查询实现。例如:
-- INTERSECT
SELECT c.first_name, c.last_name
FROM customer c
JOIN staff s
ON (c.first_name = s.first_name AND c.last_name = s.last_name);
-- EXCEPT
SELECT c.first_name, c.last_name
FROM customer c
LEFT JOIN staff s
ON (c.first_name = s.first_name AND c.last_name = s.last_name)
WHERE s.first_name IS NULL;
总结
我们可以将集合运算看作一种特殊的连接查询,而且是等值连接。正因为如此,文氏图不适合用于描述连接查询,它只描述了连接查询的一种特殊情况。