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 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!