MySQL 支持以下 JOIN
语法,用于 SELECT
语句和多表 DELETE
和 UPDATE
语句的 table_references
部分。
table_references:
escaped_table_reference [, escaped_table_reference] ...
escaped_table_reference: {
table_reference
| { OJ table_reference }
}
table_reference: {
table_factor
| joined_table
}
table_factor: {
tbl_name [PARTITION (partition_names)]
[[AS] alias] [index_hint_list]
| [LATERAL] table_subquery [AS] alias [(col_list)]
| ( table_references )
}
joined_table: {
table_reference {[INNER | CROSS] JOIN | STRAIGHT_JOIN} table_factor [join_specification]
| table_reference {LEFT|RIGHT} [OUTER] JOIN table_reference join_specification
| table_reference NATURAL [INNER | {LEFT|RIGHT} [OUTER]] JOIN table_factor
}
join_specification: {
ON search_condition
| USING (join_column_list)
}
join_column_list:
column_name [, column_name] ...
index_hint_list:
index_hint [, index_hint] ...
index_hint: {
USE {INDEX|KEY}
[FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
| {IGNORE|FORCE} {INDEX|KEY}
[FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
}
index_list:
index_name [, index_name] ...
表引用也称为联接表达式。
表引用(当它引用分区表时)可能包含一个 PARTITION
子句,其中包括一个逗号分隔的分区、子分区或两者的列表。此选项在表名之后并且在任何别名声明之前。此选项的效果是仅从列出的分区或子分区中选择行。列表中未命名的任何分区或子分区都将被忽略。有关更多信息和示例,请参见 第 26.5 节,“分区选择”。
与标准 SQL 相比,MySQL 中 table_factor
的语法有所扩展。标准仅接受 table_reference
,而不是一对括号内的列表。
如果 table_reference
项列表中的每个逗号都被视为等效于内连接,则这是一个保守的扩展。例如
SELECT * FROM t1 LEFT JOIN (t2, t3, t4)
ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)
等效于
SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4)
ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)
在 MySQL 中,JOIN
、CROSS JOIN
和 INNER JOIN
在语法上是等价的(它们可以互相替换)。在标准 SQL 中,它们并不等价。 INNER JOIN
用于与 ON
子句一起使用,CROSS JOIN
用于其他情况。
一般来说,在只包含内部联接操作的联接表达式中可以忽略括号。MySQL 也支持嵌套联接。参见 第 10.2.1.8 节,“嵌套联接优化”。
可以指定索引提示来影响 MySQL 优化器如何使用索引。有关更多信息,请参见 第 10.9.4 节,“索引提示”。优化器提示和 optimizer_switch
系统变量是影响优化器使用索引的其他方法。参见 第 10.9.3 节,“优化器提示” 和 第 10.9.2 节,“可切换优化”。
以下列表描述了编写联接时要考虑的一般因素
可以使用
或tbl_name
ASalias_name
tbl_name alias_name
对表引用进行别名化SELECT t1.name, t2.salary FROM employee AS t1 INNER JOIN info AS t2 ON t1.name = t2.name; SELECT t1.name, t2.salary FROM employee t1 INNER JOIN info t2 ON t1.name = t2.name;
table_subquery
也称为FROM
子句中的派生表或子查询。参见 第 15.2.15.8 节,“派生表”。此类子查询 必须 包含一个别名来为子查询结果提供一个表名,并且可以选择包含一个括号中的表列名列表。以下是一个简单的示例SELECT * FROM (SELECT 1, 2, 3) AS t1;
单个联接中可以引用的表数最多为 61 个。这包括通过将
FROM
子句中的派生表和视图合并到外部查询块中来处理的联接(参见 第 10.2.2.4 节,“使用合并或物化优化派生表、视图引用和通用表表达式”)。INNER JOIN
和,
(逗号)在没有联接条件的情况下在语义上是等价的:两者都会在指定的表之间产生笛卡尔积(即,第一个表中的每一行都与第二个表中的每一行联接)。但是,逗号运算符的优先级低于
INNER JOIN
、CROSS JOIN
、LEFT JOIN
等。如果将逗号联接与其他联接类型混合使用,并且存在联接条件,则可能会出现Unknown column '
形式的错误。本节后面将介绍有关处理此问题的相关信息。col_name
' in 'on clause'与
ON
一起使用的search_condition
是可以在WHERE
子句中使用的任何条件表达式。通常,ON
子句用于指定如何联接表的条件,而WHERE
子句用于限制结果集中要包含的行。如果
LEFT JOIN
中的ON
或USING
部分中没有与右侧表匹配的行,则会使用所有列都设置为NULL
的行来表示右侧表。您可以利用此特性来查找表中没有在另一个表中对应行的行SELECT left_tbl.* FROM left_tbl LEFT JOIN right_tbl ON left_tbl.id = right_tbl.id WHERE right_tbl.id IS NULL;
此示例查找
left_tbl
中所有id
值不在right_tbl
中的行(即,left_tbl
中所有没有在right_tbl
中对应行的行)。参见 第 10.2.1.9 节,“外部联接优化”。USING(
子句命名一个必须存在于两个表中的列列表。如果表join_column_list
)a
和b
都包含列c1
、c2
和c3
,则以下联接会比较两个表中的对应列a LEFT JOIN b USING (c1, c2, c3)
两个表的
NATURAL [LEFT] JOIN
被定义为在语义上等价于使用USING
子句的INNER JOIN
或LEFT JOIN
,该子句命名存在于两个表中的所有列。RIGHT JOIN
的工作原理类似于LEFT JOIN
。为了使代码在数据库之间可移植,建议使用LEFT JOIN
而不是RIGHT JOIN
。联接语法描述中显示的
{ OJ ... }
语法仅出于与 ODBC 的兼容性而存在。语法中的大括号应按字面意思编写;它们不是语法描述中使用的元语法。SELECT left_tbl.* FROM { OJ left_tbl LEFT OUTER JOIN right_tbl ON left_tbl.id = right_tbl.id } WHERE right_tbl.id IS NULL;
您可以在
{ OJ ... }
内使用其他类型的联接,例如INNER JOIN
或RIGHT OUTER JOIN
。这有助于与某些第三方应用程序兼容,但不是正式的 ODBC 语法。STRAIGHT_JOIN
类似于JOIN
,只是左表始终在右表之前读取。这可用于联接优化器以次优顺序处理表的少数情况。
一些联接示例
SELECT * FROM table1, table2;
SELECT * FROM table1 INNER JOIN table2 ON table1.id = table2.id;
SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id;
SELECT * FROM table1 LEFT JOIN table2 USING (id);
SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id
LEFT JOIN table3 ON table2.id = table3.id;
自然联接和使用 USING
的联接(包括外部联接变体)根据 SQL:2003 标准进行处理
NATURAL
联接的冗余列不会出现。考虑以下语句集CREATE TABLE t1 (i INT, j INT); CREATE TABLE t2 (k INT, j INT); INSERT INTO t1 VALUES(1, 1); INSERT INTO t2 VALUES(1, 1); SELECT * FROM t1 NATURAL JOIN t2; SELECT * FROM t1 JOIN t2 USING (j);
在第一个
SELECT
语句中,列j
出现在两个表中,因此成为联接列,所以根据标准 SQL,它应该在输出中只出现一次,而不是两次。类似地,在第二个 SELECT 语句中,列j
在USING
子句中命名,并且应该在输出中只出现一次,而不是两次。因此,这些语句产生以下输出
+------+------+------+ | j | i | k | +------+------+------+ | 1 | 1 | 1 | +------+------+------+ +------+------+------+ | j | i | k | +------+------+------+ | 1 | 1 | 1 | +------+------+------+
冗余列消除和列排序根据标准 SQL 进行,产生以下显示顺序
首先,两个联接表的合并公共列,按照它们在第一个表中出现的顺序
其次,第一个表中唯一的列,按照它们在该表中出现的顺序
第三,第二个表中唯一的列,按照它们在该表中出现的顺序
用于替换两个公共列的单个结果列是使用合并操作定义的。也就是说,对于两个
t1.a
和t2.a
,生成的单个联接列a
被定义为a = COALESCE(t1.a, t2.a)
,其中COALESCE(x, y) = (CASE WHEN x IS NOT NULL THEN x ELSE y END)
如果联接操作是任何其他联接,则联接的结果列将由所有联接表的列的串联组成。
合并列定义的一个结果是,对于外部联接,如果两个列中有一个始终为
NULL
,则合并列包含非NULL
列的值。如果两个列都为NULL
或都不为NULL
,则两个公共列具有相同的值,因此选择哪一个作为合并列的值并不重要。解释这一点的一种简单方法是考虑外部联接的合并列由JOIN
的内部表的公共列表示。假设表t1(a, b)
和t2(a, c)
具有以下内容t1 t2 ---- ---- 1 x 2 z 2 y 3 w
然后,对于此联接,列
a
包含t1.a
的值mysql> SELECT * FROM t1 NATURAL LEFT JOIN t2; +------+------+------+ | a | b | c | +------+------+------+ | 1 | x | NULL | | 2 | y | z | +------+------+------+
相反,对于此联接,列
a
包含t2.a
的值。mysql> SELECT * FROM t1 NATURAL RIGHT JOIN t2; +------+------+------+ | a | c | b | +------+------+------+ | 2 | z | y | | 3 | w | NULL | +------+------+------+
将这些结果与使用
JOIN ... ON
的等效查询进行比较mysql> SELECT * FROM t1 LEFT JOIN t2 ON (t1.a = t2.a); +------+------+------+------+ | a | b | a | c | +------+------+------+------+ | 1 | x | NULL | NULL | | 2 | y | 2 | z | +------+------+------+------+
mysql> SELECT * FROM t1 RIGHT JOIN t2 ON (t1.a = t2.a); +------+------+------+------+ | a | b | a | c | +------+------+------+------+ | 2 | y | 2 | z | | NULL | NULL | 3 | w | +------+------+------+------+
USING
子句可以改写为比较对应列的ON
子句。但是,尽管USING
和ON
类似,但它们并不完全相同。考虑以下两个查询a LEFT JOIN b USING (c1, c2, c3) a LEFT JOIN b ON a.c1 = b.c1 AND a.c2 = b.c2 AND a.c3 = b.c3
在确定哪些行满足联接条件方面,两个联接在语义上是相同的。
在确定
SELECT *
展开要显示哪些列方面,两个联接在语义上并不相同。USING
联接选择对应列的合并值,而ON
联接选择所有表中的所有列。对于USING
联接,SELECT *
选择以下值COALESCE(a.c1, b.c1), COALESCE(a.c2, b.c2), COALESCE(a.c3, b.c3)
对于
ON
联接,SELECT *
选择以下值a.c1, a.c2, a.c3, b.c1, b.c2, b.c3
对于内部联接,
COALESCE(a.c1, b.c1)
与a.c1
或b.c1
相同,因为两列具有相同的值。对于外部联接(例如LEFT JOIN
),两列中的一列可以是NULL
。该列将从结果中省略。ON
子句只能引用其操作数。示例
CREATE TABLE t1 (i1 INT); CREATE TABLE t2 (i2 INT); CREATE TABLE t3 (i3 INT); SELECT * FROM t1 JOIN t2 ON (i1 = i3) JOIN t3;
该语句会因
Unknown column 'i3' in 'on clause'
错误而失败,因为i3
是t3
中的列,而t3
不是ON
子句的操作数。为了使联接能够被处理,请将语句改写为以下形式SELECT * FROM t1 JOIN t2 JOIN t3 ON (i1 = i3);
JOIN
的优先级高于逗号运算符 (,
),因此联接表达式t1, t2 JOIN t3
被解释为(t1, (t2 JOIN t3))
,而不是((t1, t2) JOIN t3)
。这会影响使用ON
子句的语句,因为该子句只能引用联接操作数中的列,而优先级会影响对这些操作数的解释。示例
CREATE TABLE t1 (i1 INT, j1 INT); CREATE TABLE t2 (i2 INT, j2 INT); CREATE TABLE t3 (i3 INT, j3 INT); INSERT INTO t1 VALUES(1, 1); INSERT INTO t2 VALUES(1, 1); INSERT INTO t3 VALUES(1, 1); SELECT * FROM t1, t2 JOIN t3 ON (t1.i1 = t3.i3);
JOIN
的优先级高于逗号运算符,因此ON
子句的操作数是t2
和t3
。因为t1.i1
不是这两个操作数中的任何一个的列,所以结果是Unknown column 't1.i1' in 'on clause'
错误。为了使联接能够被处理,请使用以下两种策略之一
使用括号明确地对前两个表进行分组,以便
ON
子句的操作数是(t1, t2)
和t3
SELECT * FROM (t1, t2) JOIN t3 ON (t1.i1 = t3.i3);
避免使用逗号运算符,而使用
JOIN
SELECT * FROM t1 JOIN t2 JOIN t3 ON (t1.i1 = t3.i3);
相同的优先级解释也适用于将逗号运算符与
INNER JOIN
、CROSS JOIN
、LEFT JOIN
和RIGHT JOIN
混合使用的语句,所有这些语句的优先级都高于逗号运算符。与 SQL:2003 标准相比,MySQL 的一个扩展是,MySQL 允许您限定
NATURAL
或USING
联接的公共(合并)列,而标准不允许这样做。