源自MySQL 5.7 官方手冊:13.2.9.2 JOIN Syntax
SELECT select_expr From table_references JOIN... WHERE...
如上所示,MySQL支持在table_references后添加JOIN選項作為SELECT語句的一部分,當然也可以在多表的DELETE和UPDATE。
下面列出了JOIN的詳細語法:
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] | table_subquery [AS] alias | ( table_references ) joined_table: table_reference [INNER | CROSS] JOIN table_factor [join_specification] | table_reference STRAIGHT_JOIN table_factor | table_reference STRAIGHT_JOIN table_factor ON search_condition | table_reference {LEFT|RIGHT} [OUTER] JOIN table_reference join_specification | table_reference NATURAL [{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] ...
一、表引用(table reference)
一個表引用也被稱為一個JOIN表達式。表引用(當它引用分區表時)可能有PARTITION選項,包括一個由逗號分隔的分區,子分區或兩者皆有的列表。此選項緊跟在的名字之后,並在任何別名聲明之前。此選項的作用是僅從列出的分區或子分區中選擇數據行,而且將忽略列表中未命名的任何分區或子分區。see Section 22.5, “Partition Selection”。
table_factor語法是MySQL對標准SQL中的擴展。標准SQL只接受table_reference,而不是一對括號內的列表。
如果table_reference項列表中的每個逗號被視為內連接(INNER JOIN),則這是保守的擴展。例如:
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搭配其它。
一般來說,在只有INNER JOIN操作的表達式中,括號可以被省略。MySQL還支持嵌套連接,
See Section 8.2.1.7, “Nested Join Optimization”。
指定索引提示(Index hints )能夠影響MySQL優化器如何使用索引。更多信息,see Section 8.9.4, “Index Hints”.
優化器提示和optimizer_switch系統變量是影響優化器使用索引的其他方法。See Section 8.9.3, “Optimizer Hints”, and Section 8.9.2, “Switchable Optimizations”。
二、在編寫聯接時要考慮的一般因素
2.1
可以使用tbl_name AS alias_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;
2.2
table_subquery也稱為FROM子句中的派生表或子查詢。Section 13.2.10.8, “Derived Tables”.
此類子查詢必須包含別名,以便為子查詢結果提供表名。一個簡單的例子如下:
SELECT * FROM (SELECT 1, 2, 3) AS t1; /* +---+---+---+ | 1 | 2 | 3 | +---+---+---+ | 1 | 2 | 3 | +---+---+---+ */
2.3
在沒有連接條件的情況下,INNER JOIN和“,”(逗號)在語義上是等效的——兩者都在指定的表之間產生笛卡爾積,也就是說,第一個表中的每一行都連接到第二個表中的每一行。
但是,逗號運算符的優先級比其它含有“JOIN”的運算符要小。如果在存在連接條件時將逗號連接與其他連接類型混合,則可能會報錯:Unknown column 'col_name' in 'on clause' 。對這個問題的處理會在文章的后面討論。
與ON一起使用的search_condition是可以在WHERE子句中使用的任何條件表達式。ON子句用於指明如多表如何連接,WHERE子句則限制要包含在結果集中的行。
2.4
在LEFT JOIN中,如果在右表中沒有匹配ON或者USING中條件的行,則該連接中中的右表的列全都設置為NULL。你可以利用這點來查找左表A中在右表B中沒有任何對應項的行:
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的ID列中不存在。See Section 8.2.1.8, “Outer Join Optimization”.(外連接包括LEFT JOIN和RIGHT JOIN)
例如,我查找學生表stu中在成績表sc中沒有任何成績的學生:
select stu.*
from student as stu left join sc on stu.SId=sc.SId
where sc.SId is null; /* SId | Sname | Sage | Ssex | +------+-------+---------------------+------+ | 09 | 張三 | 2017-12-20 00:00:00 | 女 | | 10 | 李四 | 2017-12-25 00:00:00 | 女 | | 11 | 李四 | 2017-12-30 00:00:00 | 女 | | 12 | 趙六 | 2017-01-01 00:00:00 | 女 | | 13 | 孫七 | 2018-01-01 00:00:00 | 女 | +------+-------+---------------------+------+ */
當然這里碰到了一個小問題,把查詢語句的WHERE條件改成sc.SId=null時,取出的是空集:
select stu.* from student as stu left join sc on stu.SId=sc.SId where sc.SId=null; /* Empty set (0.08 sec) */
在WHERE子句中,column = null永遠不會為true,以這種方式使用null無效,要檢測值為NULL的列,必須使用IS NULL或列IS NOT NULL。關於NULL的使用有專門的章節:Working with NULL Values。
2.5
USING(join_column_list)子句指定兩個表中必須擁有的列的列表。如果表a和b都包含列c1,c2和c3,則以下連接將比較兩個表中的相應列:
a LEFT JOIN b USING (c1, c2, c3)
2.6
兩個表的NATURAL [LEFT] JOIN等下於下面的情況:帶有USING子句的INNER JOIN或LEFT JOIN,該子句列出了在兩個表中都存在的所有的列。
2.7
RIGHT JOIN的工作方式類似於LEFT JOIN。為了使代碼可以跨數據庫移植,建議您使用LEFT JOIN而不是RIGHT JOIN。
2.8
語法描述中的{ 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語法。
2.9
STRAIGHT_JOIN類似於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;
Natural join和使用USING的JOIN,包括外連接的變體,是根據SQL-2003的標准進行處理的。
2.10
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”列,都只會出現一次:
/* +------+------+------+ | j | i | k | +------+------+------+ | 1 | 1 | 1 | +------+------+------+ +------+------+------+ | j | i | k | +------+------+------+ | 1 | 1 | 1 | +------+------+------+ */
冗余列的消除和列的排序都是根據標准SQL進行處理,按下面的順序展示:
- 首先,合並兩個連接表的相同列,按他們在第一個表中出現的順序排列;
- 然后,第一個表所特有的列,按它們在該表中出現的順序排列;
- 第三,第二個表所特有的列,它們在該表中出現的順序;
取代兩個表的相同列的單列是通過使用coalesce(合並)操作來定義的,也就是說,對於兩個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)
如果任何其他的join操作,則連接的結果列由參與連接的表的所有列的串聯組成。合並的列的定義結果是,對於外連接,如果兩列中的一列始終為NULL,則合並列包含非NULL列的值。如果兩列都不為NULL或者都為NULL,兩個公共列具有相同的值,因此選擇哪一列作為合並列的值就無關緊要了。解釋這一點的一種簡單方法是考慮外連接的合並列由JOIN的內部表的公共列表示。
假設表t1(a,b)和t2(a,c)具有以下內容:
/* t1 t2 ---- ---- 1 x 2 z 2 y 3 w */
那么下面這個JOIN,列a包含的是t1.a的值:
SELECT * FROM t1 NATURAL LEFT JOIN t2; +------+------+------+ | a | b | c | +------+------+------+ | 1 | x | NULL | | 2 | y | z | +------+------+------+
而下面的JOIN,恰好相反,a列包含的是t2.a的值:
SELECT * FROM t1 NATURAL RIGHT JOIN t2;
+------+------+------+ | a | c | b | +------+------+------+ | 2 | z | y | | 3 | w | NULL | +------+------+------+
將這些結果與JOIN ... ON的等效查詢進行比較:
SELECT * FROM t1 LEFT JOIN t2 ON (t1.a = t2.a); +------+------+------+------+ | a | b | a | c | +------+------+------+------+ | 1 | x | NULL | NULL | | 2 | y | 2 | z | +------+------+------+------+
SELECT * FROM t1 RIGHT JOIN t2 ON (t1.a = t2.a); +------+------+------+------+ | a | b | a | c | +------+------+------+------+ | 2 | y | 2 | z | | NULL | NULL | 3 | w | +------+------+------+------+
2.11
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的JOIN,SELECT *選擇這些值:
COALESCE(a.c1, b.c1), COALESCE(a.c2, b.c2), COALESCE(a.c3, b.c3)
而使用ON的JOIN,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。該列會從結果中略去。
2.12
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;
執這個SELECT語句會報錯:Unknown column 'i3' in 'on clause' ,因為i3是t3中的一列,它不是ON子句的操作數。
對此語句進行修改:
SELECT * FROM t1 JOIN t2 JOIN t3 ON (i1 = i3);
對ON的作用范圍進行測試,以下語句均能執行:
SELECT * FROM t1 JOIN t2 JOIN t3 ON (i2 = i3); Empty set (0.00 sec) SELECT * FROM t1 JOIN t2 JOIN t3 ON (i1 = i2); Empty set (0.00 sec)
即ON對其之前的JOIN中的表的列都能引用。
2.13
JOIN比逗號操作符擁有更高的優先級,所以下面這個表達式:
t1, t2 JOIN t3
會被解釋為:
(t1, (t2 JOIN t3))
而不是:
((t1, t2) JOIN t3)
這個特點會影響使用ON子句的語句,因為ON子句只能引用JOIN操作的表中的列,優先級會影響對這些操作表的解釋。執行如下的語句就報錯了:
SELECT * FROM t1,t2 JOIN t3 ON (i1 = i2); ERROR 1054 (42S22): Unknown column 'i1' in 'on clause'
而這樣就能成功執行:
SELECT * FROM (t1,t2) JOIN t3 ON (i1 = i2); Empty set (0.00 sec)
或者不適用逗號:
SELECT * FROM t1 join t2 JOIN t3 ON (i1 = i2); Empty set (0.00 sec)
此外,INNER JOIN,CROSS JOIN,LEFT JOIN和RIGHT JOIN混合的語句中,所有這些語句的優先級都高於逗號運算符。
2.14
與SQL:2003標准相比,MySQL擴展是MySQL允許您限定NATURAL或USING連接的公共(coalesced合並)列,而標准SQL不允許這樣做。