MySQL的SQL語句 - 數據操作語句(12)- SELECT 語句(3)

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 聯接的公共(合併)列,而標準不允許這樣做。

官方網址:
https://dev.mysql.com/doc/refman/8.0/en/join.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章