基于湖库一体架构,统一管理结构化、半结构化与非结构化等多模态数据,一个系统承载事务处理、实时分析与 AI 工作负载。
联接查询
更新时间:2026-08-30 22:11:55
联接(Join)是将两个或多个表、视图或物化视图的结合在一起的查询。当查询的 FROM 子句中出现多个表时,OceanBase 数据库会进行联接查询,查询输出列可以从 FROM 子句任何表中选取。如果多个表都有一个列名,那么您必须用表名限定查询过程中对这些列的所有引用。
联接类型
数据库中的联接类型主要包括内联接、外联接和半联接(SEMI JOIN/ ANTI JOIN)和三种,其中半联接是通过子查询改写得到,OceanBase 数据库本身并没有表述相应的语法。
联接条件
联接条件是将多表结合在一起的规则,大多数联接至少包含一个联接条件,存在于 FROM 子句或 WHERE 子句中,用于比较来自不同的表两个列。WHERE 子句除了联接条件还可能含有其他条件,这些仅引用一个表的条件可以进一步限制联接查询返回的行数。
联接条件可以分为等值联接(例如 t1.a = t2.b)和非等值联接(例如 t1.a < t2.b),相比于非等值联接条件, 等值联接条件允许数据库使用高效的联接算法,例如 MERGE JOIN(MJ) 和 HASH JOIN (HJ)。
为了执行联接,OceanBase 数据库从不同表提取行并组合成对,使用联接条件进行匹配。要执行两个表以上的多表联接,OceanBase 数据库首先根据它们的列的联接条件来联接其中的两个表,然后根据包含联接表和新表列的联接条件将结果联接到另一个表。优化器根据联接条件、基表索引以及可用统计信息确定 OceanBase 数据库联接顺序。
说明
如果 WHERE 子句包含联接条件,则不能在 WHERE 子句中指定 LOB 列。
联接方式
OceanBase 数据库当前版本支持的联接方式如下表所示。
| 联接类型 | 英文表示形式 | 说明 |
|---|---|---|
| 等值联接 | EQUI JOIN | 包含等式运算符联接条件的联接。 |
| 自联接 | SELF JOIN | 表与其自身的联接。 |
| 内联接 | INNER JOIN | 内联接,结果为两个联接表中的匹配行的联接。 |
| 左(外)联接 | LEFT [OUTER] JOIN | 结果包括左表(出现在 JOIN 子句最左边)中的所有行,不包括右表中的不匹配行。 |
| 右(外)联接 | RIGHT [OUTER] JOIN | 结果包括右表(出现在 JOIN 子句最右边)中的所有行,不包括有左表中的不匹配的行。 |
| 全(外)联接 | FULL [OUTER] JOIN | 结果包括所有联接中的所有行,不论他们是否匹配。 |
| SEMI 联接 | SEMI JOIN | 只能通过子查询展开得到。 |
| ANTI 联接 | ANTI JOIN | 返回左表中不匹配右表的所有记录。 |
| 笛卡儿积 | Cartesian Product | 当两个表没有联接操作时,对这两个表进行查询得到的数据是这两个表的笛卡儿积。 |
等值联接
等值联接是包含等值运算符的联接。在进行等值联接时将指定列满足等值条件的行进行组合输出。
自联接
自联接是表与其自身的联接。该表在 FROM 子句中出现两次,后跟表别名,这些别名限定联接条件中的列名。执行自联接时,OceanBase 数据库将组合满足联接条件的行并返回。
笛卡儿积
如果联接查询中的两个表没有联接条件,OceanBase 数据库返回其笛卡尔乘积,使用第一个表的每一行与另一表每一个行进行组合输出。笛卡尔乘积会生成许多行,但是大多是无用数据。例如,两个都有 100 行的表的笛卡尔积有 10,000 行,所以除非您特别需要笛卡尔乘积,OceanBase 数据库建议您的查询中始终包括一个联接条件以避免返回笛卡尔乘积。
如果查询联接了三个或多个表,并且没有指定联接条件,则优化器可以选择一种联接顺序,以避免中间生成笛卡尔乘积。
内联接
内联接是数据库中最基本的联接操作。
内联接基于联接条件将两张表(例如 A 和 B)的列组合在一起,产生新的结果存放在表中。查询会将 A 表的每一行和 B 表的每一行进行比较,并找出满足联接条件的组合。当满足联接条件时,表 A 和 B 中匹配的行会按列组合(并排组合)成结果集中的一行。联接产生的结果集等于首先对两张表做笛卡尔积,将表 A 中的每一行和表 B 中的每一行组合,然后返回满足联接条件的记录。
外联接
外联接返回满足联接条件的所有行,同时从一个表返回没有使用的行,在另一个表相应位置填充 NULL。
外联接可依据联接表保留左表、右表或全部表的行而进一步分为左联接、右联接和全联接。其中左联接(LEFT [OUTER] JOIN)中左表行未在右表匹配到行时,在右表自动填充 NULL。右联接(RIGHT [OUTER] JOIN)中右表行未在左表匹配到行时,在左表自动填充 NULL。全联接(FULL [OUTER] JOIN)左表或者右表未在其它表匹配到行时均会填充NULL。
OceanBase 数据库还支持外联接符号(+)。对于左联接,在 WHERE 子句的联接条件中对右表的所有列应用外联接符号(+);对于右联接,在 WHERE 子句的联接条件中对左表的所有列应用外联接符号(+)。使用外联接符号(+)时会受到以下规则和限制的约束:
如果查询块中包含
FROM子句JOIN语法,则不能指定外联接符号(+)。外联接符号(+)只能出现在
WHERE子句中,或者在FROM子句的左相关上下文中(指定TABLE子句时),并且只能应用于表格或视图的一列。如果左表和右表由多个联接条件联接,则必须在所有条件中使用外联接符号(+)。否则数据库将只返回简单联接产生的行数据,而且不会出现警告或报错误。
不能使用外联接符号(+)将表外部联接到自身。
外联接运算符(+)只能应用于列,不能应用于表达式。
包含外联接符号(+)的
WHERE条件不能与使用OR和IN逻辑运算符的条件组合。在对两对以上的表执行外部联接的查询中,其中一个表可以是仅为另一个表生成的空表。因此,在 A 和 B 的联接条件以及 B 和 C 的联接条件中,不能将外联接符号 (+) 应用于 B 的列,即不能对多个联接条件中的相同列应用外联接运算符(+)。
SEMI 联接
当 A 表和 B 表进行 LEFT SEMI JOIN 或 RIGHT SEMI JOIN 的时候,它只返回 A 或 B 中所有能够在 B 或 A 中找到匹配的行。SEMI JOIN 只能通过子查询展开得到。
ANTI 联接
当 A 表和 B 表进行 LEFT ANTI JOIN 或 RIGHT ANTI JOIN 的时候,它只返回 A 或 B 中所有不能在 B 或 A 中找到匹配的行。类似于 SEMI JOIN,ANTI JOIN 也只能通过子查询展开得到。
示例
建立表 table_a 和表 table_b,并插入数据。
obclient> CREATE TABLE table_a(PK INT, name VARCHAR(25));
obclient> INSERT INTO table_a VALUES(1,'Fox');
obclient> INSERT INTO table_a VALUES(2,'Police');
obclient> INSERT INTO table_a VALUES(3,'Taxi');
obclient> INSERT INTO table_a VALUES(4,'Lincoln');
obclient> INSERT INTO table_a VALUES(5,'Arizona');
obclient> INSERT INTO table_a VALUES(6,'Washington');
obclient> INSERT INTO table_a VALUES(7,'Dell');
obclient> INSERT INTO table_a VALUES(10,'Lucent');
obclient> CREATE TABLE table_b(PK INT, name VARCHAR(25));
obclient> INSERT INTO table_b VALUES(1,'Fox');
obclient> INSERT INTO table_b VALUES(2,'Police');
obclient> INSERT INTO table_b VALUES(3,'Taxi');
obclient> INSERT INTO table_b VALUES(6,'Washington');
obclient> INSERT INTO table_b VALUES(7,'Dell');
obclient> INSERT INTO table_b VALUES(8,'Microsoft');
obclient> INSERT INTO table_b VALUES(9,'Apple');
obclient> INSERT INTO table_b VALUES(11,'Scottish Whisky');
自联接查询 示例
obclient> SELECT * FROM table_a ta, table_a tb WHERE ta.NAME = tb.NAME;
预计返回结果:
+------+------------+------+------------+
| PK | NAME | PK | NAME |
+------+------------+------+------------+
| 1 | Fox | 1 | Fox |
| 2 | Police | 2 | Police |
| 3 | Taxi | 3 | Taxi |
| 4 | Lincoln | 4 | Lincoln |
| 5 | Arizona | 5 | Arizona |
| 6 | Washington | 6 | Washington |
| 7 | Dell | 7 | Dell |
| 10 | Lucent | 10 | Lucent |
+------+------------+------+------------+
8 rows in set (0.013 sec)
内联接查询 示例
obclient> SELECT A.PK AS A_PK, A.name AS A_Value, B.PK AS B_PK, B.name AS B_Value
FROM table_a A INNER JOIN table_b B ON A.PK = B.PK;
预计返回结果:
+------+------------+------+------------+
| A_PK | A_VALUE | B_PK | B_VALUE |
+------+------------+------+------------+
| 1 | Fox | 1 | Fox |
| 2 | Police | 2 | Police |
| 3 | Taxi | 3 | Taxi |
| 6 | Washington | 6 | Washington |
| 7 | Dell | 7 | Dell |
+------+------------+------+------------+
5 rows in set (0.017 sec)
左联接查询 示例
obclient> SELECT A.PK AS A_PK, A.name AS A_Value, B.PK AS B_PK, B.name AS B_Value
FROM table_a A LEFT JOIN table_b B ON A.PK = B.PK;
预计返回结果:
+------+------------+------+------------+
| A_PK | A_VALUE | B_PK | B_VALUE |
+------+------------+------+------------+
| 1 | Fox | 1 | Fox |
| 2 | Police | 2 | Police |
| 3 | Taxi | 3 | Taxi |
| 6 | Washington | 6 | Washington |
| 7 | Dell | 7 | Dell |
| 4 | Lincoln | NULL | NULL |
| 5 | Arizona | NULL | NULL |
| 10 | Lucent | NULL | NULL |
+------+------------+------+------------+
8 rows in set (0.006 sec)
obclient> SELECT A.PK AS A_PK, A.name AS A_Value, B.PK AS B_PK, B.name AS B_Value
FROM table_a A,table_b B WHERE A.PK = B.PK(+);
预计返回结果:
+------+------------+------+------------+
| A_PK | A_VALUE | B_PK | B_VALUE |
+------+------------+------+------------+
| 1 | Fox | 1 | Fox |
| 2 | Police | 2 | Police |
| 3 | Taxi | 3 | Taxi |
| 6 | Washington | 6 | Washington |
| 7 | Dell | 7 | Dell |
| 4 | Lincoln | NULL | NULL |
| 5 | Arizona | NULL | NULL |
| 10 | Lucent | NULL | NULL |
+------+------------+------+------------+
8 rows in set (0.008 sec)
右联接查询 示例
obclient> SELECT A.PK AS A_PK, A.name AS A_Value, B.PK AS B_PK, B.name AS B_Value
FROM table_a A RIGHT JOIN table_b B ON A.PK = B.PK;
预计返回结果:
+------+------------+------+-----------------+
| A_PK | A_VALUE | B_PK | B_VALUE |
+------+------------+------+-----------------+
| 1 | Fox | 1 | Fox |
| 2 | Police | 2 | Police |
| 3 | Taxi | 3 | Taxi |
| 6 | Washington | 6 | Washington |
| 7 | Dell | 7 | Dell |
| NULL | NULL | 11 | Scottish Whisky |
| NULL | NULL | 9 | Apple |
| NULL | NULL | 8 | Microsoft |
+------+------------+------+-----------------+
8 rows in set (0.005 sec)
obclient> SELECT A.PK AS A_PK, A.name AS A_Value, B.PK AS B_PK, B.name AS B_Value
FROM table_a A,table_b B WHERE A.PK(+) = B.PK;
预计返回结果:
+------+------------+------+-----------------+
| A_PK | A_VALUE | B_PK | B_VALUE |
+------+------------+------+-----------------+
| 1 | Fox | 1 | Fox |
| 2 | Police | 2 | Police |
| 3 | Taxi | 3 | Taxi |
| 6 | Washington | 6 | Washington |
| 7 | Dell | 7 | Dell |
| NULL | NULL | 11 | Scottish Whisky |
| NULL | NULL | 9 | Apple |
| NULL | NULL | 8 | Microsoft |
+------+------------+------+-----------------+
8 rows in set (0.005 sec)
全联接查询 示例
obclient> SELECT A.PK AS A_PK,A.name AS A_Value,B.PK AS B_PK,B.name AS B_Value
FROM table_a A FULL JOIN table_b B ON A.PK = B.PK;
预计返回结果:
+------+------------+------+-----------------+
| A_PK | A_VALUE | B_PK | B_VALUE |
+------+------------+------+-----------------+
| 1 | Fox | 1 | Fox |
| 2 | Police | 2 | Police |
| 3 | Taxi | 3 | Taxi |
| 6 | Washington | 6 | Washington |
| 7 | Dell | 7 | Dell |
| NULL | NULL | 8 | Microsoft |
| NULL | NULL | 9 | Apple |
| NULL | NULL | 11 | Scottish Whisky |
| 4 | Lincoln | NULL | NULL |
| 5 | Arizona | NULL | NULL |
| 10 | Lucent | NULL | NULL |
+------+------------+------+-----------------+
11 rows in set (0.005 sec)
SEMI 联接查询示例
将有依赖关系的子查询展开改写成 SEMI JOIN。
obclient> EXPLAIN SELECT * FROM table_a t1 WHERE t1.PK IN (SELECT t2.PK FROM table_b t2
WHERE t2.NAME = t1.NAME);
预计返回结果:
+---------------------------------------------------------------------------+
| Query Plan |
+---------------------------------------------------------------------------+
| ================================================= |
| |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
| ------------------------------------------------- |
| |0 |HASH SEMI JOIN | |8 |11 | |
| |1 |├─TABLE FULL SCAN|T1 |8 |3 | |
| |2 |└─TABLE FULL SCAN|T2 |8 |3 | |
| ================================================= |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T1.PK], [T1.NAME]), filter(nil), rowset=16 |
| equal_conds([T2.NAME = T1.NAME], [T1.PK = T2.PK]), other_conds(nil) |
| 1 - output([T1.NAME], [T1.PK]), filter(nil), rowset=16 |
| access([T1.NAME], [T1.PK]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([T1.__pk_increment]), range(MIN ; MAX)always true |
| 2 - output([T2.NAME], [T2.PK]), filter(nil), rowset=16 |
| access([T2.NAME], [T2.PK]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([T2.__pk_increment]), range(MIN ; MAX)always true |
+---------------------------------------------------------------------------+
19 rows in set (0.006 sec)
ANTI 联接查询示例
将有依赖关系的子查询被改写成 ANTI JOIN。
obclient> EXPLAIN SELECT * FROM table_a t1 WHERE t1.PK NOT IN (SELECT t2.PK
FROM table_b t2 WHERE t2.name = t1.name);
预计返回结果:
+---------------------------------------------------------------------------------------------------------------+
| Query Plan |
+---------------------------------------------------------------------------------------------------------------+
| ================================================= |
| |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
| ------------------------------------------------- |
| |0 |HASH ANTI JOIN | |1 |11 | |
| |1 |├─TABLE FULL SCAN|T1 |8 |3 | |
| |2 |└─TABLE FULL SCAN|T2 |8 |3 | |
| ================================================= |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T1.PK], [T1.NAME]), filter(nil), rowset=16 |
| equal_conds([T2.NAME = T1.NAME]), other_conds([(T_OP_OR, T1.PK = T2.PK, T1.PK IS NULL, T2.PK IS NULL)]) |
| 1 - output([T1.NAME], [T1.PK]), filter(nil), rowset=16 |
| access([T1.NAME], [T1.PK]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([T1.__pk_increment]), range(MIN ; MAX)always true |
| 2 - output([T2.NAME], [T2.PK]), filter(nil), rowset=16 |
| access([T2.NAME], [T2.PK]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([T2.__pk_increment]), range(MIN ; MAX)always true |
+---------------------------------------------------------------------------------------------------------------+
19 rows in set (0.008 sec)