Mysql Join總結
首先,一張最完美的圖來展示我們可能遇到的各種連接場景,當然這幅圖不適合Mysql,因爲Mysql不支持Full Join,但是我們依然可以來理解可能遇到的各種join場景。
Mysql 連接
MySQL連接是一種基於表之間的公共列的值來鏈接來自一個(自連接)或更多表的數據的方法。
MySQL支持以下類型的連接:
- 交叉連接(Cross join)
- 內連接(Inner join)
- 左連接(Left join)
- 右連接(Right join)
要連接表,可以對相應類型的連接使用CROSS JOIN,INNER JOIN,LEFT JOIN或RIGHT JOIN子句。 在SELECT語句中的FROM子句之後使用了連接子句。
請注意,MySQL不支持完全外部連接。
數據準備
新建測試數據庫
CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET utf8 */;
新建測試數據表
use test;
#新建產品品牌分類表
CREATE TABLE `s_brand_category` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
insert into s_brand_category(`name`) values ('手機'),('電腦');
# 新建產品品牌表
CREATE TABLE `s_product_brand` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`category_id` int(11) UNSIGNED NOT NULL COMMENT '品牌分類Id' ,
`name` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `#idx_category_id` (`category_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
insert into s_product_brand(`category_id`,`name`) values(1,'小米'),(1,'華爲'),(2,'聯想');
MySQL交叉連接(CROSS JOIN)
CROSS JOIN生成來自多個表的行的笛卡爾乘積。假設您使用CROSS JOIN來連接t1和t2表,結果集將包括t1表中的行與t2表中的行的組合笛卡爾乘積。 Mysql默認的連接方式即CROSS JOIN。
mysql> select * from s_brand_category ,s_product_brand;
+----+--------+----+-------------+--------+
| id | name | id | category_id | name |
+----+--------+----+-------------+--------+
| 1 | 手機 | 1 | 1 | 小米 |
| 2 | 電腦 | 1 | 1 | 小米 |
| 1 | 手機 | 2 | 1 | 華爲 |
| 2 | 電腦 | 2 | 1 | 華爲 |
| 1 | 手機 | 3 | 2 | 聯想 |
| 2 | 電腦 | 3 | 2 | 聯想 |
+----+--------+----+-------------+--------+
6 rows in set (0.01 sec)
mysql> select * from s_brand_category cross join s_product_brand;
+----+--------+----+-------------+--------+
| id | name | id | category_id | name |
+----+--------+----+-------------+--------+
| 1 | 手機 | 1 | 1 | 小米 |
| 2 | 電腦 | 1 | 1 | 小米 |
| 1 | 手機 | 2 | 1 | 華爲 |
| 2 | 電腦 | 2 | 1 | 華爲 |
| 1 | 手機 | 3 | 2 | 聯想 |
| 2 | 電腦 | 3 | 2 | 聯想 |
+----+--------+----+-------------+--------+
6 rows in set (0.00 sec)
MySQL內連接(INNER JOIN)
要形成一個INNER JOIN連接子句,需要一個稱爲連接謂詞的條件。 INNER JOIN需要兩個連接的表中的行具有匹配的列值。 INNER JOIN通過組合基於連接謂詞的兩個連接表的列值來創建結果集。
要連接兩個表,INNER JOIN將第一個表中的每一行與第二個表中的每一行進行比較,以找到滿足連接謂詞的行對。每當通過匹配非NULL值來滿足連接謂詞時,兩個表中每個匹配的行對的列值將包含在結果集中(可以簡單地理解爲兩個表的交集).
當沒有連接謂詞的存在的時候INNER JOIN 得到的結果即是CROSS JOIN。
mysql> select * from s_brand_category inner join s_product_brand;
+----+--------+----+-------------+--------+
| id | name | id | category_id | name |
+----+--------+----+-------------+--------+
| 1 | 手機 | 1 | 1 | 小米 |
| 2 | 電腦 | 1 | 1 | 小米 |
| 1 | 手機 | 2 | 1 | 華爲 |
| 2 | 電腦 | 2 | 1 | 華爲 |
| 1 | 手機 | 3 | 2 | 聯想 |
| 2 | 電腦 | 3 | 2 | 聯想 |
+----+--------+----+-------------+--------+
6 rows in set (0.00 sec)
mysql> select * from s_brand_category as c inner join s_product_brand as b on c.id = b.category_id;
+----+--------+----+-------------+--------+
| id | name | id | category_id | name |
+----+--------+----+-------------+--------+
| 1 | 手機 | 1 | 1 | 小米 |
| 1 | 手機 | 2 | 1 | 華爲 |
| 2 | 電腦 | 3 | 2 | 聯想 |
+----+--------+----+-------------+--------+
3 rows in set (0.00 sec)
MySQL左連接(LEFT JOIN)
類似於INNER JOIN,LEFT JOIN也需要連接謂詞。當使用LEFT JOIN連接兩個表時,介紹了左表和右表的概念。
與INNER JOIN不同,LEFT JOIN返回左表中的所有行,包括滿足連接謂詞的行。 對於不匹配連接謂詞的行,右表中的列將使用NULL值顯示在結果集中。
不同於Inner Join,Left Join 必須有連接謂詞ON的存在。
# 先插入一條種類不存在的產品記錄
insert into s_product_brand(`category_id`,`name`) values(3,'特步');
mysql> select * from s_product_brand as b left join s_brand_category as c on c.id = b.category_id;
+----+-------------+--------+------+--------+
| id | category_id | name | id | name |
+----+-------------+--------+------+--------+
| 1 | 1 | 小米 | 1 | 手機 |
| 2 | 1 | 華爲 | 1 | 手機 |
| 3 | 2 | 聯想 | 2 | 電腦 |
| 4 | 3 | 特步 | NULL | NULL |
+----+-------------+--------+------+--------+
4 rows in set (0.00 sec)
mysql> select * from s_brand_category as c left join s_product_brand as b on c.id = b.category_id;
+----+--------+------+-------------+--------+
| id | name | id | category_id | name |
+----+--------+------+-------------+--------+
| 1 | 手機 | 1 | 1 | 小米 |
| 1 | 手機 | 2 | 1 | 華爲 |
| 2 | 電腦 | 3 | 2 | 聯想 |
+----+--------+------+-------------+--------+
3 rows in set (0.00 sec)
MySQL右連接(RIGHT JOIN)
右連接(RIGHT JOIN)類似於左連接(LEFT JOIN),除了表的處理是相反的。使用RIGHT JOIN,右表格(t2)中的每一行將顯示在結果集中。 對於右表中沒有左表(t1)中的匹配行的行,左表(t1)中的列會顯示NULL。
不同於Inner Join,Right Join 必須有連接謂詞ON的存在。
mysql> select * from s_product_brand as b right join s_brand_category as c on c.id = b.category_id;
+------+-------------+--------+----+--------+
| id | category_id | name | id | name |
+------+-------------+--------+----+--------+
| 1 | 1 | 小米 | 1 | 手機 |
| 2 | 1 | 華爲 | 1 | 手機 |
| 3 | 2 | 聯想 | 2 | 電腦 |
+------+-------------+--------+----+--------+
3 rows in set (0.00 sec)
mysql> select * from s_brand_category as c right join s_product_brand as b on c.id = b.category_id;
+------+--------+----+-------------+--------+
| id | name | id | category_id | name |
+------+--------+----+-------------+--------+
| 1 | 手機 | 1 | 1 | 小米 |
| 1 | 手機 | 2 | 1 | 華爲 |
| 2 | 電腦 | 3 | 2 | 聯想 |
| NULL | NULL | 4 | 3 | 特步 |
+------+--------+----+-------------+--------+
ON 與Where
這裏,我們先注意在左右連接查詢中 on和where未知順序不同導致的查詢結果的不同。
mysql> select * from s_product_brand as b left join s_brand_category as c on b.category_id = c.id and c.id = 1;
+----+-------------+--------+------+--------+
| id | category_id | name | id | name |
+----+-------------+--------+------+--------+
| 1 | 1 | 小米 | 1 | 手機 |
| 2 | 1 | 華爲 | 1 | 手機 |
| 3 | 2 | 聯想 | NULL | NULL |
| 4 | 3 | 特步 | NULL | NULL |
+----+-------------+--------+------+--------+
4 rows in set (0.00 sec)
mysql> select * from s_product_brand as b left join s_brand_category as c on b.category_id = c.id where c.id = 1;
+----+-------------+--------+------+--------+
| id | category_id | name | id | name |
+----+-------------+--------+------+--------+
| 1 | 1 | 小米 | 1 | 手機 |
| 2 | 1 | 華爲 | 1 | 手機 |
+----+-------------+--------+------+--------+
2 rows in set (0.00 sec)
我們會發現兩個查詢存在差異。
爲什麼會存在差異,其實這和on與where查詢順序有關。
首先,我們需要知道:
標準的 SQL 解析順序爲:
- FROM 子句 組裝來自不同數據源的數據
- WHERE 子句 基於指定的條件對記錄進行篩選
- GROUP BY 子句 將數據劃分爲多個分組
- 使用聚合函數進行計算
- 使用HAVING子句篩選分組
- 計算所有的表達式
- 使用ORDER BY對結果集進行排序
標準的 SQL 執行順序爲:
- FROM:對FROM子句中前兩個表執行笛卡爾積生成虛擬表vt1
- ON: 對vt1表應用ON篩選器只有滿足 join_condition 爲真的行才被插入vt2
- OUTER(join):如果指定了 OUTER JOIN保留表(preserved table)中未找到的行將行作爲外部行添加到vt2,生成t3,如果from包含兩個以上表,則對上一個聯結生成的結果表和下一個表重複執行步驟和步驟直接結束。
- WHERE:對vt3應用 WHERE 篩選器只有使 where_condition 爲true的行才被插入vt4
- GROUP BY:按GROUP BY子句中的列列表對vt4中的行分組生成vt5
- CUBE|ROLLUP:把超組(supergroups)插入vt6,生成vt6
- HAVING:對vt6應用HAVING篩選器只有使 having_condition 爲true的組才插入vt7
- SELECT:處理select列表產生vt8
- DISTINCT:將重複的行從vt8中去除產生vt9
- ORDER BY:將vt9的行按order by子句中的列列表排序生成一個遊標vc10
所以這裏的根本原因在於
先on條件篩選錶行再join
而對於where是對 join之後結果做再次篩選。
ON與where的使用一定要注意場所:
- ON後面的篩選條件主要是針對的是關聯表【而對於主表刷選條件不適用】。
- 對於主表的篩選條件應放在where後面,不應該放在ON後面
- 對於關聯表我們要區分對待。如果是要條件查詢後才連接應該把查詢件放置於ON後。如果是想再連接完畢後才篩選就應把條件放置於where後面
- 對於關聯表我們其實可以先做子查詢再做join
mysql> select * from s_product_brand as b left join (select * from s_brand_category where id = 1) as c on b.category_id = c.id ;
+----+-------------+--------+------+--------+
| id | category_id | name | id | name |
+----+-------------+--------+------+--------+
| 1 | 1 | 小米 | 1 | 手機 |
| 2 | 1 | 華爲 | 1 | 手機 |
| 3 | 2 | 聯想 | NULL | NULL |
| 4 | 3 | 特步 | NULL | NULL |
+----+-------------+--------+------+--------+
4 rows in set (0.00 sec)
mysql> select * from s_product_brand as b left join s_brand_category as c on b.category_id = c.id and c.id = 1;
+----+-------------+--------+------+--------+
| id | category_id | name | id | name |
+----+-------------+--------+------+--------+
| 1 | 1 | 小米 | 1 | 手機 |
| 2 | 1 | 華爲 | 1 | 手機 |
| 3 | 2 | 聯想 | NULL | NULL |
| 4 | 3 | 特步 | NULL | NULL |
+----+-------------+--------+------+--------+
4 rows in set (0.00 sec)