数据库中的连接语句用于将数据库中的两个或多个表根据连接条件组合起来。由“连接”生成的集合, 可以被保存为表,或者当成表来使用。连接语句的含义是把两张表的属性通过它们的值组合在一起。数据库中的连接类型一般包括内连接(Inner-Join),外连接(Outer-Join),Semi 连接(Semi-Join)和 Anti 连接(Anti-Join)。其中 Semi-Join 和 Anti-Join 都是通过子查询改写得到,SQL 本身并没有表述 Anti-Join 和 Semi-Join 的语法。
连接条件
连接条件可以分为等值连接(比如 t1.a = t2.b)和非等值连接(t1.a < t2.b)。相比于非等值连接条件,等值连接条件的一个好处是允许数据库中使用高效的连接算法,比如 Hash Join 和 Merge-Sort Join。
Self-Join
Self-Join 是指跟跟自己表做连接的 Join。下面展示了一个 Self-Join 的例子。
obclient> create table t1(a int primary key, b int, c int);
Query OK, 0 rows affected (0.70 sec)
obclient> select * from t1 as ta, t1 as tb where ta.b = tb.b;
内连接(Inner-Join)
Inner-Join 是数据库中最基本的连接操作。内连接基于连接条件将两张表(如 A 和 B)的列组合在一起,产生新的结果表。查询会将 A 表的每一行和 B 表的每一行进行比较,并找出满足连接条件的组合。当连接条件被满足,A 和 B 中匹配的行会按列组合(并排组合)成结果集中的一行。连接产生的结果集,可以定义为首先对两张表做笛卡尔积(交叉连接,将 A 中的每一行和 B 中的每一行组合),然后返回满足连接条件的记录。
外连接(Outer-Join)
Outer-Join 并不要求连接的两表的每一条记录在对方表中都一条匹配的记录。要保留所有记录(甚至这条记录没有匹配的记录也要保留)的表称为保留表。外连接可依据连接表保留左表,右表或全部表的行而进一步分为左外连接,右外连接和全连接。其中左外连接中左表的一行未在右表中找到的时候,就在右表自动填充 NULL。右外连接中右表的一行未在左表中找到的时候,就在左表自动填充 NULL。全连接就是左表或者右表找不匹配行的时候都会自动填充 NULL。
Semi 连接 (Semi-Join)
当 A 表和 B 表进行 left/right Semi-Join 的时候,它只返回 A/B 表中所有能够在 B/A 中找到匹配的行。Semi-Join 只能通过子查询展开得到,如下所示。
obclient> create table t1(a int primary key, b int, c int);
Query OK, 0 rows affected (0.70 sec)
obclient> create table t2(a int primary key, b int, c int);
Query OK, 0 rows affected (0.92 sec)
obclient> insert into t1 values(1, 1, 1);
obclient> insert into t1 values(2, 2, 2);
obclient> insert into t2 values(1, 1, 1);
obclient> insert into t2 values(2, 2, 2);
--- 有依赖关系的子查询被展开改写成 Semi-Join
obclient> explain select * from t1 where t1.a in (select t2.b from t2 where t2.c = t1.c);
| ========================================
|ID|OPERATOR |NAME|EST. ROWS|COST|
----------------------------------------
|0 |MERGE SEMI JOIN| |2 |76 |
|1 | TABLE SCAN |t1 |2 |37 |
|2 | SORT | |2 |38 |
|3 | TABLE SCAN |t2 |2 |37 |
========================================
Outputs & filters:
-------------------------------------
0 - output([t1.a], [t1.b], [t1.c]), filter(nil),
equal_conds([t1.a = t2.b], [t2.c = t1.c]), other_conds(nil)
1 - output([t1.c], [t1.a], [t1.b]), filter(nil),
access([t1.c], [t1.a], [t1.b]), partitions(p0)
2 - output([t2.b], [t2.c]), filter(nil), sort_keys([t2.b, ASC], [t2.c, ASC])
3 - output([t2.c], [t2.b]), filter(nil),
access([t2.c], [t2.b]), partitions(p0)
Anti 连接(Anti-Join)
当 A 表和 B 表进行 left/right Anti-Join 的时候,它只返回 A/B 中所有不能再 B/A 中找到匹配的行。类似于 Semi-Join,Anti-Join 也只能通过子查询展开得到,如下所示。
---有依赖关系的子查询被改写成 Anti-Join
obclient> explain select * from t1 where t1.a not in (select t2.b from t2 where t2.c = t1.c);
| =============================================
|ID|OPERATOR |NAME|EST. ROWS|COST|
---------------------------------------------
|0 |HASH RIGHT ANTI JOIN| |0 |77 |
|1 | TABLE SCAN |t2 |2 |37 |
|2 | TABLE SCAN |t1 |2 |37 |
=============================================
Outputs & filters:
-------------------------------------
0 - output([t1.a], [t1.b], [t1.c]), filter(nil),
equal_conds([t2.c = t1.c]), other_conds([t1.a = t2.b OR (T_OP_IS, t2.b, NULL, 0)])
1 - output([t2.c], [t2.b]), filter(nil),
access([t2.c], [t2.b]), partitions(p0)
2 - output([t1.c], [t1.a], [t1.b]), filter(nil),
access([t1.c], [t1.a], [t1.b]), partitions(p0)