- JOIN語句的執行順序
- INNER/LEFT/RIGHT/FULL JOIN的區別
- ON和WHERE的區別
一個完整的SQL語句中會被拆分紅多個子句,子句的執行過程當中會產生虛擬表(vt),可是結果只返回最後一張虛擬表。從這個思路出發,咱們試着理解一下JOIN查詢的執行過程並解答一些常見的問題。
若是以前對不一樣JOIN的執行結果沒有概念,能夠結合這篇文章往下看html
如下是JOIN查詢的通用結構mysql
SELECT <row_list> FROM <left_table> <inner|left|right> JOIN <right_table> ON <join condition> WHERE <where_condition>
它的執行順序以下(SQL語句裏第一個被執行的老是FROM子句):sql
下面用一個例子介紹一下上述聯表的過程(這個例子不是個好的實踐,只是爲了說明join語法)編程
建立一個用戶信息表:code
CREATE TABLE `user_info` ( `userid` int(11) NOT NULL, `name` varchar(255) NOT NULL, UNIQUE `userid` (`userid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
再建立一個用戶餘額表:htm
CREATE TABLE `user_account` ( `userid` int(11) NOT NULL, `money` bigint(20) NOT NULL, UNIQUE `userid` (`userid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
隨便導入一些數據:索引
select * from user_info; +--------+------+ | userid | name | +--------+------+ | 1001 | x | | 1002 | y | | 1003 | z | | 1004 | a | | 1005 | b | | 1006 | c | | 1007 | d | | 1008 | e | +--------+------+ 8 rows in set (0.00 sec) select * from user_account; +--------+-------+ | userid | money | +--------+-------+ | 1001 | 22 | | 1002 | 30 | | 1003 | 8 | | 1009 | 11 | +--------+-------+ 4 rows in set (0.00 sec)
一共8個用戶有用戶名,4個用戶的帳戶有餘額。
取出userid爲1003的用戶姓名和餘額,SQL以下:ip
SELECT i.name, a.money FROM user_info as i LEFT JOIN user_account as a ON i.userid = a.userid WHERE a.userid = 1003;
笛卡爾積操做後會返回兩張表中全部行的組合,左表user_info有8行,右表user_account有4行,生成的虛擬表vt1就是8*4=32行:文檔
SELECT * FROM user_info as i LEFT JOIN user_account as a ON 1; +--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1001 | x | 1001 | 22 | | 1002 | y | 1001 | 22 | | 1003 | z | 1001 | 22 | | 1004 | a | 1001 | 22 | | 1005 | b | 1001 | 22 | | 1006 | c | 1001 | 22 | | 1007 | d | 1001 | 22 | | 1008 | e | 1001 | 22 | | 1001 | x | 1002 | 30 | | 1002 | y | 1002 | 30 | | 1003 | z | 1002 | 30 | | 1004 | a | 1002 | 30 | | 1005 | b | 1002 | 30 | | 1006 | c | 1002 | 30 | | 1007 | d | 1002 | 30 | | 1008 | e | 1002 | 30 | | 1001 | x | 1003 | 8 | | 1002 | y | 1003 | 8 | | 1003 | z | 1003 | 8 | | 1004 | a | 1003 | 8 | | 1005 | b | 1003 | 8 | | 1006 | c | 1003 | 8 | | 1007 | d | 1003 | 8 | | 1008 | e | 1003 | 8 | | 1001 | x | 1009 | 11 | | 1002 | y | 1009 | 11 | | 1003 | z | 1009 | 11 | | 1004 | a | 1009 | 11 | | 1005 | b | 1009 | 11 | | 1006 | c | 1009 | 11 | | 1007 | d | 1009 | 11 | | 1008 | e | 1009 | 11 | +--------+------+--------+-------+ 32 rows in set (0.00 sec)
ON i.userid = a.userid 過濾以後vt2以下:get
+--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1001 | x | 1001 | 22 | | 1002 | y | 1002 | 30 | | 1003 | z | 1003 | 8 | +--------+------+--------+-------+
LEFT JOIN會將左表未出如今vt2的行插入進vt2,每一行的剩餘字段將被填充爲NULL,RIGHT JOIN同理
本例中用的是LEFT JOIN,因此會將左表user_info剩下的行都添上 生成表vt3:
+--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1001 | x | 1001 | 22 | | 1002 | y | 1002 | 30 | | 1003 | z | 1003 | 8 | | 1004 | a | NULL | NULL | | 1005 | b | NULL | NULL | | 1006 | c | NULL | NULL | | 1007 | d | NULL | NULL | | 1008 | e | NULL | NULL | +--------+------+--------+-------+
WHERE a.userid = 1003 生成表vt4:
+--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1003 | z | 1003 | 8 | +--------+------+--------+-------+
SELECT i.name, a.money 生成vt5:
+------+-------+ | name | money | +------+-------+ | z | 8 | +------+-------+
虛擬表vt5做爲最終結果返回給客戶端
介紹完聯表的過程以後,咱們看看經常使用JOIN的區別
拿上文的第三步添加外部行來舉例,若LEFT JOIN替換成INNER JOIN,則會跳過這一步,生成的表vt3與vt2如出一轍:
+--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1001 | x | 1001 | 22 | | 1002 | y | 1002 | 30 | | 1003 | z | 1003 | 8 | +--------+------+--------+-------+
若LEFT JOIN替換成RIGHT JOIN,則生成的表vt3以下:
+--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1001 | x | 1001 | 22 | | 1002 | y | 1002 | 30 | | 1003 | z | 1003 | 8 | | NULL | NULL | 1009 | 11 | +--------+------+--------+-------+
由於user_account(右表)裏存在userid=1009這一行,而user_info(左表)裏卻找不到這一行的記錄,因此會在第三步插入如下一行:
| NULL | NULL | 1009 | 11 |
上文引用的文章中提到了標準SQL定義的FULL JOIN,這在mysql裏是不支持的,不過咱們能夠經過LEFT JOIN + UNION + RIGHT JOIN 來實現FULL JOIN:
SELECT * FROM user_info as i RIGHT JOIN user_account as a ON a.userid=i.userid union SELECT * FROM user_info as i LEFT JOIN user_account as a ON a.userid=i.userid;
他會返回以下結果:
+--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1001 | x | 1001 | 22 | | 1002 | y | 1002 | 30 | | 1003 | z | 1003 | 8 | | NULL | NULL | 1009 | 11 | | 1004 | a | NULL | NULL | | 1005 | b | NULL | NULL | | 1006 | c | NULL | NULL | | 1007 | d | NULL | NULL | | 1008 | e | NULL | NULL | +--------+------+--------+-------+
ps:其實咱們從語義上就能看出LEFT JOIN和RIGHT JOIN沒什麼差異,二者的結果差別取決於左右表的放置順序,如下內容摘自mysql官方文檔:
RIGHT JOIN works analogously to LEFT JOIN. To keep code portable across databases, it is recommended that you use LEFT JOIN instead of RIGHT JOIN.
因此當你糾結使用LEFT JOIN仍是RIGHT JOIN時,儘量只使用LEFT JOIN吧
上文把JOIN的執行順序瞭解清楚以後,ON和WHERE的區別也就很好理解了。
舉例說明:
SELECT * FROM user_info as i LEFT JOIN user_account as a ON i.userid = a.userid and i.userid = 1003;
SELECT * FROM user_info as i LEFT JOIN user_account as a ON i.userid = a.userid where i.userid = 1003;
第一種狀況LEFT JOIN在執行完第二步ON子句後,篩選出知足i.userid = a.userid and i.userid = 1003的行,生成表vt2,而後執行第三步JOIN子句,將外部行添加進虛擬表生成vt3即最終結果:
vt2: +--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1003 | z | 1003 | 8 | +--------+------+--------+-------+ vt3: +--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1001 | x | NULL | NULL | | 1002 | y | NULL | NULL | | 1003 | z | 1003 | 8 | | 1004 | a | NULL | NULL | | 1005 | b | NULL | NULL | | 1006 | c | NULL | NULL | | 1007 | d | NULL | NULL | | 1008 | e | NULL | NULL | +--------+------+--------+-------+
而第二種狀況LEFT JOIN在執行完第二步ON子句後,篩選出知足i.userid = a.userid的行,生成表vt2;再執行第三步JOIN子句添加外部行生成表vt3;而後執行第四步WHERE子句,再對vt3表進行過濾生成vt4,得的最終結果:
vt2: +--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1001 | x | 1001 | 22 | | 1002 | y | 1002 | 30 | | 1003 | z | 1003 | 8 | +--------+------+--------+-------+ vt3: +--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1001 | x | 1001 | 22 | | 1002 | y | 1002 | 30 | | 1003 | z | 1003 | 8 | | 1004 | a | NULL | NULL | | 1005 | b | NULL | NULL | | 1006 | c | NULL | NULL | | 1007 | d | NULL | NULL | | 1008 | e | NULL | NULL | +--------+------+--------+-------+ vt4: +--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1003 | z | 1003 | 8 | +--------+------+--------+-------+
若是將上例的LEFT JOIN替換成INNER JOIN,不論將條件過濾放到ON仍是WHERE裏,結果都是同樣的,由於INNER JOIN不會執行第三步添加外部行
SELECT * FROM user_info as i INNER JOIN user_account as a ON i.userid = a.userid and i.userid = 1003;
SELECT * FROM user_info as i INNER JOIN user_account as a ON i.userid = a.userid where i.userid = 1003;
返回結果都是:
+--------+------+--------+-------+ | userid | name | userid | money | +--------+------+--------+-------+ | 1003 | z | 1003 | 8 | +--------+------+--------+-------+
《MySQL技術內幕:SQL編程》
SQL Joins - W3Schools
sql - What is the difference between 「INNER JOIN」 and 「OUTER JOIN」?
MySQL :: MySQL 8.0 Reference Manual :: 13.2.10.2 JOIN Syntax
Visual Representation of SQL Joins
Join (SQL) - Wikipedia)