一、Join语法概述
join 用于多表中字段之间的联系,语法如下:
... FROMtable1INNER|LEFT|RIGHTJOINtable2ONconditiona
able1:左表;table2:右表。
JOIN 按照功能大致分为如下三类:
INNER JOIN(内连接或等值连接):取得两个表中存在连接匹配关系的记录。
LEFT JOIN(左连接):取得左表(table1)完全记录,即是右表(table2)并无对应匹配记录。
RIGHT JOIN(右连接):与 LEFT JOIN 相反,取得右表(table2)完全记录,即是左表(table1)并无匹配对应记录。
注意:mysql不支持Full join,不过可以通过UNION 关键字来合并 LEFT JOIN 与 RIGHT JOIN来模拟FULL join.
Ps:我们了解到在 MySQL 中,INNER JOIN...ON,JOIN...ON,逗号...WHERE,CROSS JOIN...ON 是一样的含义。但是在标准 SQL 中,它们并不等价。
当然“逗号...WHERE”和上述的“JOIN”之间底层还是有区别的
JOIN 是生成一张临时表,然后 WHERE 进一步筛选,所以如果在 JOIN 的时候就能筛选掉的话(替 WHERE 分担一些性能开销)那是最好不过了。
接下来给出一个列子用于解释下面几种分类。如下两个表(A,B)
mysql>selectA.id,A.name,B.namefromA,BwhereA.id=B.id; +----+-----------+-------------+|id|name|name|+----+-----------+-------------+|1|Pirate|Rutabaga||2|Monkey|Pirate||3|Ninja|DarthVader||4|Spaghetti|Ninja|+----+-----------+-------------+4rowsinset (0.00sec)
二、Inner join
内连接,也叫等值连接,inner join产生同时符合A和B的一组数据。
mysql>select*fromAinnerjoinBonA.name=B.name; +----+--------+----+--------+|id|name|id|name|+----+--------+----+--------+|1|Pirate|2|Pirate||3|Ninja|4|Ninja|+----+--------+----+--------+
三、Left join
mysql>select*fromAleftjoinBonA.name=B.name; +----+-----------+------+--------+|id|name|id|name|+----+-----------+------+--------+|1|Pirate|2|Pirate||2|Monkey|NULL|NULL||3|Ninja|4|Ninja||4|Spaghetti|NULL|NULL|+----+-----------+------+--------+4rowsinset (0.00sec)
left join,(或left outer join:在Mysql中两者等价,推荐使用left join.)左连接从左表(A)产生一套完整的记录,与匹配的记录(右表(B)) .如果没有匹配,右侧将包含null。
如果想只从左表(A)中产生一套记录,但不包含右表(B)的记录,可以通过设置where语句来执行,如下:
mysql>select*fromAleftjoinBonA.name=B.namewhereA.idisnullorB.idisnull; +----+-----------+------+------+|id|name|id|name|+----+-----------+------+------+|2|Monkey|NULL|NULL||4|Spaghetti|NULL|NULL|+----+-----------+------+------+2rowsinset (0.00sec)
同理,还可以模拟inner join. 如下:
mysql>select*fromAleftjoinBonA.name=B.namewhereA.idisnotnullandB.idisnotnull; +----+--------+------+--------+|id|name|id|name|+----+--------+------+--------+|1|Pirate|2|Pirate||3|Ninja|4|Ninja|+----+--------+------+--------+2rowsinset (0.00sec)
求差集:
根据上面的例子可以求差集,如下:
SELECT*FROMALEFTJOINBONA.name=B.nameWHEREB.idISNULLunionSELECT*FROMArightJOINBONA.name=B.nameWHEREA.idISNULL; +------+-----------+------+-------------+|id|name|id|name|+------+-----------+------+-------------+|2|Monkey|NULL|NULL||4|Spaghetti|NULL|NULL||NULL|NULL|1|Rutabaga||NULL|NULL|3|DarthVader|+------+-----------+------+-------------+
四、Right join
mysql>select*fromArightjoinBonA.name=B.name; +------+--------+----+-------------+|id|name|id|name|+------+--------+----+-------------+|NULL|NULL|1|Rutabaga||1|Pirate|2|Pirate||NULL|NULL|3|DarthVader||3|Ninja|4|Ninja|+------+--------+----+-------------+4rowsinset (0.00sec)
同left join。
五、Cross join
cross join:交叉连接,得到的结果是两个表的乘积,即笛卡尔积
笛卡尔(Descartes)乘积又叫直积。假设集合A={a,b},集合B={0,1,2},则两个集合的笛卡尔积为{(a,0),(a,1),(a,2),(b,0),(b,1), (b,2)}。可以扩展到多个集合的情况。类似的例子有,如果A表示某学校学生的集合,B表示该学校所有课程的集合,则A与B的笛卡尔积表示所有可能的选课情况。
mysql>select*fromAcrossjoinB; +----+-----------+----+-------------+|id|name|id|name|+----+-----------+----+-------------+|1|Pirate|1|Rutabaga||2|Monkey|1|Rutabaga||3|Ninja|1|Rutabaga||4|Spaghetti|1|Rutabaga||1|Pirate|2|Pirate||2|Monkey|2|Pirate||3|Ninja|2|Pirate||4|Spaghetti|2|Pirate||1|Pirate|3|DarthVader||2|Monkey|3|DarthVader||3|Ninja|3|DarthVader||4|Spaghetti|3|DarthVader||1|Pirate|4|Ninja||2|Monkey|4|Ninja||3|Ninja|4|Ninja||4|Spaghetti|4|Ninja|+----+-----------+----+-------------+16rowsinset (0.00sec)
实际上,在 MySQL 中(仅限于 MySQL) CROSS JOIN 与 INNER JOIN 的表现是一样的,在不指定 ON 条件得到的结果都是笛卡尔积,反之取得两个表各自匹配的结果。
INNER JOIN(或者用“,”表示)与 CROSS JOIN 可以省略 INNER 或 CROSS 关键字,因此下面的 SQL 效果是一样的:
... FROMtable1INNERJOINtable2... FROMtable1CROSSJOINtable2... FROMtable1JOINtable2
六、Full join
mysql>select*fromAleftjoinBonB.name=A.name->union->select*fromArightjoinBonB.name=A.name; +------+-----------+------+-------------+|id|name|id|name|+------+-----------+------+-------------+|1|Pirate|2|Pirate||2|Monkey|NULL|NULL||3|Ninja|4|Ninja||4|Spaghetti|NULL|NULL||NULL|NULL|1|Rutabaga||NULL|NULL|3|DarthVader|+------+-----------+------+-------------+6rowsinset (0.00sec)
全连接产生的所有记录(双方匹配记录)在表A和表B。如果没有匹配,则对面将包含null。
MySQL本身不支持full join(全连接),但可以通过union来实现。
七、性能优化
1、显示(explicit) inner join VS 隐式(implicit) inner join
如:
select*fromtableainnerjointablebona.id=b.id;
VS
selecta.*, b.*fromtablea, tablebwherea.id=b.id;
我在数据库中比较(10w数据)得之,它们用时几乎相同,第一个是显示的inner join,后一个是隐式的inner join。
2.left join/right join VS inner join
尽量用inner join.避免 LEFT JOIN 和 NULL.
在使用left join(或right join)时,应该清楚的知道以下几点:
(1) on 与 where 的执行顺序
ON 条件(“A LEFT JOIN B ON 条件表达式”中的ON)用来决定如何从 B 表中检索数据行。如果 B 表中没有任何一行数据匹配 ON 的条件,将会额外生成一行所有列为 NULL 的数据,在匹配阶段 WHERE 子句的条件都不会被使用。仅在匹配阶段完成以后,WHERE 子句条件才会被使用。它将从匹配阶段产生的数据中检索过滤。
所以我们要注意:在使用Left (right) join的时候,一定要在先给出尽可能多的匹配满足条件,减少Where的执行。如:
PS:这部分有些不妥,感谢 wxweven 指正:
这部分的内容,博主写的有些欠妥当,不知道博主有没有实际运行测试过,下面说说我的看法:
(1)首先关于on和where的用法,如果直接把where里面的条件拿到on里面去,结果是跟原来的不一致的,所以博主说的“在使用Left (right) join的时候,一定要在先给出尽可能多的匹配满足条件,减少Where的执行”是不成立的,因为筛选条件放在on或者where,产生的是不同的结果,不能说为了性能就把where中的条件放到on中。
PASS
select*fromAinnerjoinBonB.name=A.nameleftjoinConC.name=B.nameleftjoinDonD.id=C.idwhereC.status>1andD.status=1;
Great
select*fromAinnerjoinBonB.name=A.nameleftjoinConC.name=B.nameandC.status>1leftjoinDonD.id=C.idandD.status=1
从上面例子可以看出,尽可能满足ON的条件,而少用Where的条件。从执行性能来看第二个显然更加省时。
(2) 注意 ON 子句和 WHERE 子句的不同
如作者举了一个列子:
mysql>SELECT*FROMproductLEFTJOINproduct_detailsON (product.id=product_details.id) ANDproduct_details.id=2; +----+--------+------+--------+-------+|id|amount|id|weight|exist|+----+--------+------+--------+-------+|1|100|NULL|NULL|NULL||2|200|2|22|0||3|300|NULL|NULL|NULL||4|400|NULL|NULL|NULL|+----+--------+------+--------+-------+4rowsinset (0.00sec) mysql>SELECT*FROMproductLEFTJOINproduct_detailsON (product.id=product_details.id) WHEREproduct_details.id=2; +----+--------+----+--------+-------+|id|amount|id|weight|exist|+----+--------+----+--------+-------+|2|200|2|22|0|+----+--------+----+--------+-------+1rowinset (0.01sec)
从上可知,第一条查询使用 ON 条件决定了从 LEFT JOIN的 product_details表中检索符合的所有数据行。第二条查询做了简单的LEFT JOIN,然后使用 WHERE 子句从 LEFT JOIN的数据中过滤掉不符合条件的数据行。
(3) 尽量避免子查询,而用join
往往性能这玩意儿,更多时候体现在数据量比较大的时候,此时,我们应该避免复杂的子查询。如下:
PASS
insertintot1(a1) selectb1fromt2wherenotexists(select1fromt1wheret1.id=t2.r_id);
Great
insertintot1(a1) selectb1fromt2leftjoin (selectdistinctt1.idfromt1 ) t1ont1.id=t2.r_idwheret1.idisnull;
附: