1. 程式人生 > 其它 >mysql之left join、join的on、where區別看這篇就懂

mysql之left join、join的on、where區別看這篇就懂

技術標籤:資料庫mysql

mysql之left join、join的on、where區別看這篇就懂

前言:

網上大量關於left join、join的on、where區別其實很多都是錯誤,本文開始揭曉其中區別所在,該如何使用。

1.準備

建表語句

CREATE TABLE `t_students` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `class_id` int(11) NOT NULL,
  `name` varchar(10) NOT NULL,
  `gender` char(1) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_class_id` (`class_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8

CREATE TABLE `t_classes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8

表資料

在這裡插入圖片描述

在這裡插入圖片描述

2. Join連線on、where區別

sql1

SELECT * FROM `t_students` ts JOIN `t_classes` tc ON ts.`class_id` = tc.`id`;

使用explain extended + sql;

select_typetabletypepossible_keyskeykey_lenrefrowsExtra
SIMPLEtcALL(NULL)(NULL)(NULL)(NULL)11
SIMPLEtsALLPRIMARY(NULL)(NULL)(NULL)5Using where; Using join buffer

show warnings:

select `mytest`.`ts`.`id` AS `id`,`mytest`.`ts`.`class_id` AS `class_id`,`mytest`.`ts`.`name` AS `name`,`mytest`.`ts`.`gender` AS `gender`,`mytest`.`tc`.`id` AS `id`,`mytest`.`tc`.`name` AS `name` 

from `mytest`.`t_students` `ts` join `mytest`.`t_classes` `tc` 

where (`mytest`.`tc`.`id` = `mytest`.`ts`.`class_id`)

分析:從show warnings查詢InnoDB優化後的語句,可以發現on連線條件被轉化為where過濾條件。更多案例,可以自己去測試,on都會轉化為where

結論:對於Join連線,on和where其實是一樣的,經過InnoDB優化後,on連線條件會轉化為where。

3. left join之on、where區別
  • 驅動表之on、where區別

sql1

SELECT * FROM `t_students` ts 
LEFT JOIN `t_classes` tc ON ts.`class_id` = tc.`id` AND ts.`gender` = 'M';

使用explain extended + sql;

select_typetabletypepossible_keyskeykey_lenrefrowsExtra
SIMPLEtsALL(NULL)(NULL)(NULL)(NULL)11
SIMPLEtceq_refPRIMARYPRIMARY4mytest.ts.class_id1

show warnings:

select `mytest`.`ts`.`id` AS `id`,`mytest`.`ts`.`class_id` AS `class_id`,`mytest`.`ts`.`name` AS `name`,`mytest`.`ts`.`gender` AS `gender`,`mytest`.`tc`.`id` AS `id`,`mytest`.`tc`.`name` AS `name` 

from `mytest`.`t_students` `ts` left join `mytest`.`t_classes` `tc` 

on(((`mytest`.`ts`.`class_id` = `mytest`.`tc`.`id`) and (`mytest`.`ts`.`gender` = 'M'))) 

where 1

結果集:

在這裡插入圖片描述

分析:從結果集來看,ts.gender = ‘M’ 並未生效。為什麼?

從explian分析看出,ts作為驅動表,做全表掃描,然後把查詢到的每條記錄的ts.class_id、ts.gender = ‘M’(也就是on裡面的)作為條件讓被驅動表tc做單表查詢(ts有多少條記錄,單表查詢多少次)得到結果集。

這裡可以看出,left join連線on連線條件是給被驅動表用的,ts.gender = 'M’放在on裡面,對驅動表查詢是無效,僅在連線被驅動表時生效,這不是我們想要的結果。

那我們應該怎麼改sql,讓ts.gender = 'M’對驅動表生效。請看sql2

sql2

SELECT * FROM `t_students` ts LEFT JOIN `t_classes` tc
ON ts.`class_id` = tc.`id` WHERE ts.`gender` = 'M';

使用explain extended + sql;

select_typetabletypepossible_keyskeykey_lenrefrowsExtra
SIMPLEtsALL(NULL)(NULL)(NULL)(NULL)11Using where
SIMPLEtceq_refPRIMARYPRIMARY4mytest.ts.class_id1

可以看出,ts表Extra使用了Using where

show warnings:

select `mytest`.`ts`.`id` AS `id`,`mytest`.`ts`.`class_id` AS `class_id`,`mytest`.`ts`.`name` AS `name`,`mytest`.`ts`.`gender` AS `gender`,`mytest`.`tc`.`id` AS `id`,`mytest`.`tc`.`name` AS `name` 

from `mytest`.`t_students` `ts` left join `mytest`.`t_classes` `tc`

on((`mytest`.`tc`.`id` = `mytest`.`ts`.`class_id`)) 

where (`mytest`.`ts`.`gender` = 'M')

結果集:

在這裡插入圖片描述

分析

從explian分析看出,ts作為驅動表,把ts.gender = 'M’作為條件做全表掃描,然後把查詢到的每條記錄的ts.class_id(也就是on裡面的)作為條件讓被驅動表tc做單表查詢(ts有多少條記錄,單表查詢多少次)得到結果集。

這裡可以看出,ts.gender = 'M’放在where裡面,驅動表做全表掃描時會帶上where裡面條件。

結論:對於驅動表,需要加只針對驅動表的過濾條件,我們應該放在where裡面而不是on裡面

  • 被驅動表之on、where區別

sql1

SELECT * FROM `t_students` ts LEFT JOIN `t_classes` tc
ON ts.`class_id` = tc.`id` AND tc.`name` IN ( '二班', '三班');

使用explain extended + sql;

select_typetabletypepossible_keyskeykey_lenrefrowsExtra
SIMPLEtsALL(NULL)(NULL)(NULL)(NULL)11
SIMPLEtceq_refPRIMARYPRIMARY4mytest.ts.class_id1

show warnings:

select `mytest`.`ts`.`id` AS `id`,`mytest`.`ts`.`class_id` AS `class_id`,`mytest`.`ts`.`name` AS `name`,`mytest`.`ts`.`gender` AS `gender`,`mytest`.`tc`.`id` AS `id`,`mytest`.`tc`.`name` AS `name` 

from `mytest`.`t_students` `ts` left join `mytest`.`t_classes` `tc` 

on(((`mytest`.`ts`.`class_id` = `mytest`.`tc`.`id`) and (`mytest`.`tc`.`name` in ('二班','三班')))) 

where 1

結果集:

在這裡插入圖片描述

分析

從explian分析看出,ts作為驅動表,做全表掃描,然後把查詢到的每條記錄的ts.class_id、tc.name in (‘二班’,‘三班’)(也就是on裡面的)作為條件讓被驅動表tc做單表查詢(ts有多少條記錄,單表查詢多少次)得到結果集。

假如:被驅動表的過濾條件放在where 而不是on呢,請看如下sql

sql2

SELECT * FROM `t_students` ts LEFT JOIN `t_classes` tc
ON ts.`class_id` = tc.`id` WHERE tc.`name` IN ( '二班', '三班');

使用explain extended + sql;

select_typetabletypepossible_keyskeykey_lenrefrowsExtra
SIMPLEtsALL(NULL)(NULL)(NULL)(NULL)11
SIMPLEtcALLPRIMARY(NULL)(NULL)(NULL)5Using where; Using join buffer

show warnings:

select `mytest`.`ts`.`id` AS `id`,`mytest`.`ts`.`class_id` AS `class_id`,`mytest`.`ts`.`name` AS `name`,`mytest`.`ts`.`gender` AS `gender`,`mytest`.`tc`.`id` AS `id`,`mytest`.`tc`.`name` AS `name` 

from `mytest`.`t_students` `ts` join `mytest`.`t_classes` `tc` 

where ((`mytest`.`tc`.`id` = `mytest`.`ts`.`class_id`) and (`mytest`.`tc`.`name` in ('二班','三班')))

結果集:

在這裡插入圖片描述

分析

從show warnings分析看出,如果被驅動表有過濾條件在where,那麼left join會失效,InnoDB優化成join連線。所以,被驅動表的過濾條件應該放在on而不是where

4. 附加

網上有種說法:left join連線 on會先生成虛擬表,然後再經過where條件過濾生成結果集。

這種說法是錯誤的!

驗證:

sql

SELECT * FROM `t_classes` tc LEFT JOIN `t_students` ts 
ON ts.`class_id` = tc.`id` WHERE ts.id = NULL
  • 虛擬表生成:
SELECT * FROM `t_classes` tc LEFT JOIN `t_students` ts 
ON ts.`class_id` = tc.`id`

結果集如下:

在這裡插入圖片描述

  • 再經過
WHERE ts.id = NULL

生成結果集,應該如下:

在這裡插入圖片描述

然而我們執行這條sql 生成的結果集卻是如下所示:

在這裡插入圖片描述

原因:被驅動表的過濾條件放在where的話,left join被優化成了join。