MySQL LEFT JOIN/ INNER JOIN/RIGHT JOIN
概述
一个完整的SQL语句会被拆分成很多个子句,子句在执行过程中会产生多个临时表(vt),但是结果只返回最后一张临时表。从这个思路出发,我们试着理解一下JOIN查询的执行过程并解答一些常见的问题。
JOIN 执行顺序
JOIN查询的通用结构:
SELECT <row_list>
FROM <left_table> <left|inner|right>
JOIN <right_table>
ON <join_condition>
WHERE <where_condition>
它的执行顺序如下(SQL语句里第一个被执行的总是FROM子句):
- FROM:对左右俩表执行笛卡尔积,产生第一个临时表vt1,行数为n*m条,(n为左表的行数,m为右表的行数)。
- ON:根据on条件对vt1表进行过滤,结构插入到vt2表。
- JOIN:添加外部行。如果指定了LEFT JOIN(LEFT OUTER JOIN),则先遍历一遍左表的每一行,其中不在vt2的行会被插入到vt2,该行的剩余字段将被填充为NULL,形成vt3;如果指定了RIGHT JOIN也是同理。但如果指定的是INNER JOIN,则不会添加外部行,上述插入过程被忽略,vt2=vt3(所以INNER JOIN的过滤条件放在ON或WHERE里 执行结果是没有区别的,下文会细说)
- WHERE,根据where条件对vt3表筛选,生成vt4表。
- SELECT,取出vt4的指定字段到vt5
举例
创建一个用户信息表:
CREATE TABLE `user_info` (
`userid` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
UNIQUE `userid` (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
创建一个用户余额表:
CREATE TABLE `user_account` (
`userid` int(11) NOT NULL,
`money` bigint(20) NOT NULL,
UNIQUE `userid` (`userid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
导入一些数据:
user_info:
|userid | name |
| -------- | -------- |
| 10001 | x |
| 10002 | y |
| 10003 | z |
| 10004 | a |
| 10005 | b |
| 10006 | c |
| 10007 | d |
| 10008 | e |
user_account:
| userid | money |
| -------- | -------- |
| 1001 | 22 |
| 1002 | 30 |
| 1003 | 8 |
| 1009 | 11 |
一共8个用户有用户名,4个用户的账户有余额。需求:找出userid=1003的用户名和余额。SQL如下:
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;
第一步:执行FROM从句,对俩个表进行笛卡尔积操作。
笛卡尔积操作后会返回俩个表所有行对组合,左表user_info有8行,右表user_account有4行,生成的虚拟表vt1就是8*4=32行:
| 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 |
第二步,执行ON,把不满足条件对行去掉,生成vt2表:
| userid | name | userid | money |
| -------- | -------- | -------- | -------- |
| 1001 | x | 1001 | 22 |
| 1002 | y | 1002 | 30 |
| 1003 | z | 1003 | 8 |
第三步,执行JOIN,进行添加外部行。
LEFT JOIN遍历左表user_info,把所有左表中没有出现在vt2表中的行插入到vt2表中,每一行的剩余字段将会用NULL填充,生成vt3表。RIGHT JOIN也是同理。
| userid | name | userid | money |
| -------- | -------- | -------- | -------- |
| 1001 | x | 1001 | 22 |
| 1002 | y | 1002 | 30 |
| 1003 | z | 1003 | 8 |
| 10004 | a | NULL | NULL |
| 10005 | b | NULL | NULL |
| 10006 | c | NULL | NULL |
| 10007 | d | NULL | NULL |
| 10008 | e | NULL | NULL |
第四步,执行WHERE条件 userid = 1003 筛选,生成vt4表。
| userid | name | userid | money |
| -------- | -------- | -------- | -------- |
| 1003 | z | 1003 | 8 |
第五步,执行SELECT 查询具体字段。生成vt5将作为结果返回。
| name | money |
| -------- | -------- |
| z | 8 |
INNER/LEFT/RIGHT/FULL JOIN的区别。
INNER JOIN...ON...: 返回俩表关联的所有行,不执行上面说的第三部JOIN添加外部行。
LEFT JOIN...ON... : 返回左表中的所有行,若有些行在右表中没有对应的值,将会使用NULL填充。
RIGHT JOIN...ON...: 返回右表中的所有行,若有些行在左表中没有对应的值,将会使用NULL填充。
INNER JOIN
拿上文的第三步添加外部行来举例,若LEFT JOIN替换成INNER JOIN,则会跳过这一步,生成的表vt3与vt2一模一样:
| userid | name | userid | money |
| -------- | -------- | -------- | -------- |
| 1001 | x | 1001 | 22 |
| 1002 | y | 1002 | 30 |
| 1003 | z | 1003 | 8 |
RIGHT JOIN
若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(左表)里却找不到这一行的记录,所以会在第三步插入以下一行:
| userid | name | userid | money |
| -------- | -------- | -------- | -------- |
| NULL | NULL | 1009 | 11 |
我们可以看出其实LEFT JOIN 和 RIGHT JOIN 没有什么区别,所以尽量使用LEFT 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 |
原文地址
本作品采用《CC 协议》,转载必须注明作者和本文链接