JOIN 子句
MySQL 對 SELECT 語句和多表 DELETE 和 UPDATE 語句 table_references 部分支持以下 JOIN 語法:
1. table_references:
2. escaped_table_reference [, escaped_table_reference] ...
3.
4. escaped_table_reference: {
5. table_reference
6. | { OJ table_reference }
7. }
8.
9. table_reference: {
10. table_factor
11. | joined_table
12. }
13.
14. table_factor: {
15. tbl_name [PARTITION (partition_names)]
16. [[AS] alias] [index_hint_list]
17. | [LATERAL] table_subquery [AS] alias [(col_list)]
18. | ( table_references )
19. }
20.
21. joined_table: {
22. table_reference {[INNER | CROSS] JOIN | STRAIGHT_JOIN} table_factor [join_specification]
23. | table_reference {LEFT|RIGHT} [OUTER] JOIN table_reference join_specification
24. | table_reference NATURAL [INNER | {LEFT|RIGHT} [OUTER]] JOIN table_factor
25. }
26.
27. join_specification: {
28. ON search_condition
29. | USING (join_column_list)
30. }
31.
32. join_column_list:
33. column_name [, column_name] ...
34.
35. index_hint_list:
36. index_hint [, index_hint] ...
37.
38. index_hint: {
39. USE {INDEX|KEY}
40. [FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
41. | {IGNORE|FORCE} {INDEX|KEY}
42. [FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
43. }
44.
45. index_list:
46. index_name [, index_name] ...
表引用也稱爲聯接表達式。
表引用(當它引用分區表時)可以包含 PARTITION 選項,包括逗號分隔的分區、子分區列表。此選項緊跟在表名之後,並位於任何別名之前。這個選項的效果是隻從列出的分區或子分區中選擇行。忽略列表中未命名的任何分區或子分區。
與標準 SQL 相比,MySQL 擴展了 table_factor 的語法。標準 SQL 只接受 table_reference,而不接受一對括號內的列表。
如果 table_reference 項目列表中的每個逗號都被視爲等同於內部聯接,則這是一個保守的擴展。例如:
1. SELECT * FROM t1 LEFT JOIN (t2, t3, t4)
2. ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)
等價於:
1. SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4)
2. 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 還支持嵌套連接。
可以指定索引提示來影響 MySQL 優化器如何使用索引。
下表描述了寫聯接語句時要考慮的一般因素:
● 可以使用 tbl_name AS alias_name 或 tbl_name alias_name 對錶引用使用別名:
1. SELECT t1.name, t2.salary
2. FROM employee AS t1 INNER JOIN info AS t2 ON t1.name = t2.name;
3.
4. SELECT t1.name, t2.salary
5. FROM employee t1 INNER JOIN info t2 ON t1.name = t2.name;
● table_subquery 在 FROM 子句中也稱爲派生表或子查詢。此類子查詢必須包含別名,以便爲子查詢結果提供表名,並且可以選擇在括號中包含表的列名。下面是一個簡單的例子:
1. SELECT * FROM (SELECT 1, 2, 3) AS t1;
● 單個聯接中可引用的最大表數量爲 61。這包括通過將 FROM 子句中的派生表和視圖合併到外部查詢塊來處理的聯接。
● 在沒有連接條件的情況下,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。可以使用此事實來查找表中在另一個表中沒有對應項的行:
1. SELECT left_tbl.*
2. FROM left_tbl LEFT JOIN right_tbl ON left_tbl.id = right_tbl.id
3. WHERE right_tbl.id IS NULL;
此示例查找 left_tbl 中 id 值不在 right_tbl 中的所有行(也就是說,left_tbl 中的所有行在 right butl 都中沒有對應的行)。
● USING(join_column_list) 子句指定兩個表中必須存在的列名的列表。如果表a和表b都包含列c1、c2和c3,則下面的聯接將比較兩個表中的相應列:
1. a LEFT JOIN b USING (c1, c2, c3)
● 兩個表的 NATURAL [LEFT] JOIN 被定義爲語義上等價於 INNER JOIN 或帶有 USING 子句的 LEFT JOIN,USING 子句命名兩個表中存在的所有列。
● RIGHT JOIN 的工作原理與 LEFT JOIN 類似。爲了保持代碼在數據庫之間的可移植性,建議使用 LEFT JOIN 而不是 RIGHT JOIN。
● 聯接語法描述中顯示的 { OJ ... } 語法僅用於與 ODBC 兼容。語法中的大括號應該按字面意思寫;它們不是語法描述中其他地方使用的元語法。
1. SELECT left_tbl.*
2. FROM { OJ left_tbl LEFT OUTER JOIN right_tbl
3. ON left_tbl.id = right_tbl.id }
4. WHERE right_tbl.id IS NULL;
可以在 { OJ ... } 中使用其他類型的連接,例如 INNER JOIN 或 RIGHT OUTER JOIN。這有助於與某些第三方應用程序兼容,但不是官方的 ODBC 語法。
● STRAIGHT_JOIN 類似於 JOIN,只是左表總是在右表之前讀取。這可以用於連接優化器以次優順序處理表的那些(少數)情況。
一些連接示例:
1. SELECT * FROM table1, table2;
2.
3. SELECT * FROM table1 INNER JOIN table2 ON table1.id = table2.id;
4.
5. SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id;
6.
7. SELECT * FROM table1 LEFT JOIN table2 USING (id);
8.
9. SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id
10. LEFT JOIN table3 ON table2.id = table3.id;
自然聯接和使用 USING 的聯接,包括外部聯接變體,根據 SQL:2003 標準處理:
● NATURAL 聯接的冗餘列不會出現。參考這組語句:
1. CREATE TABLE t1 (i INT, j INT);
2. CREATE TABLE t2 (k INT, j INT);
3. INSERT INTO t1 VALUES(1, 1);
4. INSERT INTO t2 VALUES(1, 1);
5. SELECT * FROM t1 NATURAL JOIN t2;
6. SELECT * FROM t1 JOIN t2 USING (j);
在第一個 SELECT 語句中,j 列出現在兩個表中,因此成爲一個聯接列,因此,根據標準 SQL,它應該只在輸出中出現一次,而不是兩次。類似地,在第二個 SELECT 語句中,列 j 在 USING 子句中命名,並且應該只在輸出中出現一次,而不是兩次。
因此,這些語句產生以下輸出:
1. +------+------+------+
2. | j | i | k |
3. +------+------+------+
4. | 1 | 1 | 1 |
5. +------+------+------+
6. +------+------+------+
7. | j | i | k |
8. +------+------+------+
9. | 1 | 1 | 1 |
10. +------+------+------+
根據標準 SQL 進行冗餘列消除和列排序,生成以下顯示順序:
■ 首先,按照它們在第一個表中出現的順序,合併兩個連接表的公共列
■ 第二,按照它們在該表中出現的順序排列第一個表所特有的列
■ 第三,按照它們在該表中出現的順序排列第二個表所特有的列
替換兩個公共列,獲得單個結果列是使用合併操作定義的。也就是說,對於兩個 t1.a 和 t2.a,生成的單個聯接列 a 定義爲 a = COALESCE(t1.a, t2.a),其中:
1. COALESCE(x, y) = (CASE WHEN x IS NOT NULL THEN x ELSE y END)
如果聯接操作是任何其他聯接,則聯接的結果列由聯接表的所有列的組成。
合併列定義的結果是,對於外部聯接,如果兩個列中的一個始終爲 NULL,則合併列包含非 NULL 列的值。如果兩列都爲 NULL 或都不爲 NULL,則兩個公共列都具有相同的值,因此選擇哪一列作爲合併列的值無關緊要。解釋這種情況的一種簡單方法是,考慮外部聯接的合併列由聯接的內部表的公共列表示。假設表 t1(a, b) 和 t2(a, c) 具有以下內容:
1.t1 t2
2.---- ----
3.1 x 2 z
4.2 y 3 w
然後,對於這個連接,a 列包含 t1.a 的值:
1.mysql> SELECT * FROM t1 NATURAL LEFT JOIN t2;
2. +------+------+------+
3. | a | b | c |
4. +------+------+------+
5. | 1 | x | NULL |
6. | 2 | y | z |
7. +------+------+------+
相比之下,對於這個連接,列 a 包含 t2.a 的值。
1. 1. mysql> SELECT * FROM t1 NATURAL RIGHT JOIN t2 ON (t1.a = t2.a);
2. +------+------+------+------+
3. | a | b | a | c |
4. +------+------+------+------+
5. | 1 | x | NULL | NULL |
6. | 2 | y | 2 | z |
7. +------+------+------+------+
1.mysql> SELECT * FROM t1 RIGHT JOIN t2 ON (t1.a = t2.a);
2.+------+------+------+------+
3.| a | b | a | c |
4.+------+------+------+------+
5.| 2 | y | 2 | z |
6.| NULL | NULL | 3 | w |
7.+------+------+------+------+
● USING 子句可以重寫爲比較相應列的 ON 子句。然而,儘管 USING 和 ON 是相似的,但它們並不完全相同。考慮以下兩個查詢:
1. a LEFT JOIN b USING (c1, c2, c3)
2.a LEFT JOIN b ON a.c1 = b.c1 AND a.c2 = b.c2 AND a.c3 = b.c3
關於確定哪些行滿足聯接條件,兩個聯接在語義上是相同的。
關於確定要爲 SELECT 顯示哪些列,這兩個聯接在語義上是不相同的。USING 聯接選擇相應列的合併值,而 ON 聯接選擇所有表中的所有列。對於 USING 聯接,SELECT 選擇以下值:
1.COALESCE(a.c1, b.c1), COALESCE(a.c2, b.c2), 2.COALESCE(a.c3, b.c3)
對於 ON 聯接,SELECT * 選擇以下值:
1.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 子句只能引用其操作數。
例子:
1.CREATE TABLE t1 (i1 INT);
2.CREATE TABLE t2 (i2 INT);
3.CREATE TABLE t3 (i3 INT);
4.SELECT * FROM t1 JOIN t2 ON (i1 = i3) JOIN t3;
語句失敗並出現 Unknown column 'i3' in 'on clause' 錯誤,因爲 i3 是 t3 中的列,而 t3 不是 ON 子句的操作數。要使聯接能夠被處理,請重寫語句,如下所示:
1.SELECT * FROM t1 JOIN t2 JOIN t3 ON (i1 = i3);
● JOIN 的優先級高於逗號運算符 (,),因此連接表達式 t1, t2 JOIN t3 解釋爲 (t1, (t2 JOIN t3)),而不是 ((t1, t2) JOIN t3)。這會影響使用 ON 子句的語句,因爲該子句只能引用聯接操作數中的列,優先級會影響對這些操作數的解析。
例子:
1.CREATE TABLE t1 (i1 INT, j1 INT);
2. CREATE TABLE t2 (i2 INT, j2 INT);
3. CREATE TABLE t3 (i3 INT, j3 INT);
4. INSERT INTO t1 VALUES(1, 1);
5. INSERT INTO t2 VALUES(1, 1);
6. INSERT INTO t3 VALUES(1, 1);
7. 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:
1. SELECT * FROM (t1, t2) JOIN t3 ON (t1.i1 = t3.i3);
■ 避免使用逗號運算符,改用 JOIN:
1. SELECT * FROM t1 JOIN t2 JOIN t3 ON (t1.i1 = t3.i3);
同樣的優先級解釋也適用於將逗號運算符與 INNER JOIN、CROSS JOIN、LEFT JOIN 和 RIGHT JOIN 混合使用的語句,這些語句的優先級都高於逗號運算符。
● MySQL 擴展了 SQL:2003 標準,允許限定自然聯接或 USING 聯接的公共(合併)列,而標準不允許這樣做。