深入淺出SQL(13)-外聯接、自聯接與聯合
外聯接、自聯接與聯合:新策略
關於聯接我們只認識了一半:
我們已經知道 返回每個可能行的交叉聯接,返回兩張表中相符記錄的內聯接;
但還沒見過外聯接,他可以在表中沒有匹配記錄的情況下返回記錄;
還有自聯接,他可以聯接表本身;
還有聯合,可以合併查詢結果;
學習了這些之後,就可以用自己的方式來取得所有資料;本章還會探討下子查詢;
測試資料庫我們使用上一章的mark_list,不熟悉的可以移步看下;
找出my_contacts表中任何記錄連線的職業?
任何記錄是指每一條記錄,不管這條記錄是否有對應的職業;
這種資訊可以通過外聯接(outer join)取得;
我們知道,使用內連線的時候,雖然要比對來自兩張表的行,但表的順序並無影響;
我們新插入一條聯絡人資訊,用以測試:(該表之前刪除過一條資料)
我們使用內聯接,找到的my_contacts表中有職業的記錄,SQL如下:
這實際上是一個相等聯接,比較的是聯絡人的contact_id;
mysql> SELECT mc.last_name,mc.first_name,jc.title -> FROM my_contacts mc INNER JOIN job_current jc -> ON mc.contact_id = jc.contact_id; +-----------+------------+-----------+ | last_name | first_name | title | +-----------+------------+-----------+ | Joy | HQ | EngineerM | | Mary | DM | EngineerS | +-----------+------------+-----------+
外聯接:
外聯接返回某張表的所有行,並且有來自另一張表的條件相符的資料;
一切都跟左右有關:
外聯接更注重兩張表之間的關係;
左外聯接(LEFT OUTER JOIN):
接收左邊的所有行,並用這些行與右邊匹配;
當左表與右表具有一對多關係時,左外聯接特別有用;
在LEFT OUTER JOIN中,出現在FROM後、聯接前的表稱為左表,出現在聯接後的表為右表;
嘗試使用左外聯接回答一開始的問題:
mysql> SELECT mc.last_name,mc.first_name,jc.title -> FROM my_contacts mc LEFT OUTER JOIN job_current jc -> ON mc.contact_id = jc.contact_id; +-----------+------------+-----------+ | last_name | first_name | title | +-----------+------------+-----------+ | Joy | HQ | EngineerM | | Mary | DM | EngineerS | | July | FM | NULL | +-----------+------------+-----------+
外聯接和內聯接的差別:
外聯接一定會提供資料行,無論該行能否在另一個表中找到相匹配的行;
出現NULL表示沒有匹配的行,上述查詢的NULL表示July沒有工作;
左外聯接的結果集中的NULL表示右表中沒有找到與左表相符的記錄;
左表的所有行都會存在,符合條件的資料結果表中會有相應資料,不符合條件的則填入NULL;
列的顯示順序可以用SELECT語句控制,更重要的是聯接表的順序;
外聯接與多個相符結果:
我們使用測試表boys和toys;
執行兩個方向的左外聯接:
下面兩張圖展示了左外聯接的實際行動;
以第二張圖為例,說明如下:
wan ju 1->條件不符->NULL;
wan ju 2->條件相符->boy1;
wan ju 2->條件相符->boy2;
wan ju 2->條件相符->boy3;
wan ju 3->條件相符->boy5;
wan ju 4->條件相符->boy4;
wan ju 5->條件不符->NULL;
右外聯接(RIGHT OUTER JOIN):
與左外聯接基本一致,區別在於它是使用右表與左表比對;
在RIGHT OUTER JOIN中,出現在FROM後、聯接前的表稱為右表,出現在聯接後的表為左表;
因此同樣的是左表聯接右表,對boys和toys使用左、右外聯接的SQL如下:
mysql> SELECT b.boy,t.toy
-> FROM boys b LEFT OUTER JOIN toys t
-> ON b.toy_id = t.toy_id;
mysql> SELECT b.boy,t.toy
-> FROM toys t RIGHT OUTER JOIN boys b
-> ON b.toy_id = t.toy_id;
查詢的結果集,二者是一致的:
+-------+----------+
| boy | toy |
+-------+----------+
| boy 1 | wan ju 2 |
| boy 2 | wan ju 2 |
| boy 3 | wan ju 2 |
| boy 4 | wan ju 4 |
| boy 5 | wan ju 3 |
+-------+----------+
全連線(FULL OUTER JOIN):
少數的幾個RDBMS能夠做到,MySQL不行;
同一個表可以同時作為外聯接的左右表
我們新建一張測試表clown_boss,記錄員工及其boss:
相關SQL如下:
1)新建表:
mysql> CREATE TABLE clown_boss
-> (
-> id INT NOT NULL AUTO_INCREMENT,
-> name VARCHAR(30),
-> boss_id INT NOT NULL,
-> PRIMARY KEY(id)
-> );
2)新增資料:
mysql> INSERT INTO clown_boss
-> (name,boss_id)
-> VALUES
-> ('B1',5),
-> ('B2',1),
-> ('B3',1),
-> ('B4',3),
-> ('B5',5);
3)更新表結構,新增外來鍵:
mysql> ALTER TABLE clown_boss
-> ADD CONSTRAINT clown_boss_clown_boss_id_fk FOREIGN KEY(boss_id) REFERENCES clown_boss(id);
4)查看錶結構:
mysql> DESC clown_boss;
+---------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(30) | YES | | NULL | |
| boss_id | int(11) | NO | MUL | NULL | |
+---------+-------------+------+-----+---------+----------------+
5)再插入兩條資料:
mysql> INSERT INTO clown_boss
-> (name,boss_id)
-> VALUES
-> ('B6',2),
-> ('B7',2);
測試表如下:
mysql> SELECT * FROM clown_boss;
+----+------+---------+
| id | name | boss_id |
+----+------+---------+
| 1 | B1 | 5 |
| 2 | B2 | 1 |
| 3 | B3 | 1 |
| 4 | B4 | 3 |
| 5 | B5 | 5 |
| 6 | B6 | 2 |
| 7 | B7 | 2 |
+----+------+---------+
自引用外來鍵:
我們注意到上張表的主鍵和外來鍵都來自clown_boss表;
boss_id是引用了同一張表主鍵id的外來鍵——我們叫自引用外來鍵(self-referencing foreign key);
自引用外來鍵是出於其他目的而用於同一張表的主鍵;
聯接表與他自己:
獲取小丑的資訊及其頭領資訊;
可以想成是兩張相同的表,各自設定別名;
mysql> SELECT cb1.name clown ,cb2.name boss
-> FROM clown_boss cb1
-> INNER JOIN clown_boss cb2
-> ON cb1.boss_id = cb2.id;
+-------+------+
| clown | boss |
+-------+------+
| B1 | B5 |
| B2 | B1 |
| B3 | B1 |
| B4 | B3 |
| B5 | B5 |
| B6 | B2 |
| B7 | B2 |
+-------+------+
我們需要自聯接:
使用表clown_boss兩次,上邊的SQL對應的就是自聯接;
自聯接能把單一表當成兩張具有完全相同的資訊的表來進行查詢;
聯合-取得多張表內容:
聯合UNION,也以用來取得多張表的查詢結果;
UNION可以根據在SELECT中指定的列,把兩張或更多張表的查詢結果合併至一個表中;
比如,我們要獲取資料庫mark_list中,三張工作表中的所有工作;
1)分開的SELECT查詢:
2)使用聯合:
mysql> SELECT title FROM job_current
-> UNION
-> SELECT title FROM job_desired
-> UNION
-> SELECT title FROM job_list;
+-----------+
| title |
+-----------+
| EngineerM |
| EngineerS |
| EngineerH |
| EngineerW |
| EngineerY |
| EngineerA |
+-----------+
如果需要排序,只需要在最後加上ORDER BY;
mysql> SELECT title FROM job_current
-> UNION
-> SELECT title FROM job_desired
-> UNION
-> SELECT title FROM job_list
-> ORDER BY title;
+-----------+
| title |
+-----------+
| EngineerA |
| EngineerH |
| EngineerM |
| EngineerS |
| EngineerW |
| EngineerY |
+-----------+
同時我們也注意到,查詢結果並沒有重複值,這是因為SQL預設會清除聯合中的重複值;
如果需要看重複資料,可以使用UNION ALL運算子:
mysql> SELECT title FROM job_current
-> UNION ALL
-> SELECT title FROM job_desired
-> UNION ALL
-> SELECT title FROM job_list
-> ORDER BY title;
+-----------+
| title |
+-----------+
| EngineerA |
| EngineerH |
| EngineerM |
| EngineerM |
| EngineerM |
| EngineerM |
| EngineerS |
| EngineerS |
| EngineerS |
| EngineerS |
| EngineerW |
| EngineerY |
+-----------+
UNION的使用規則:
1)UNION只接受一個ORDER BY且位於語句末端,這是因為UNION已經把多個SELECT語句的查詢結果串起來並進行了分組;(預設進行了GROUP BY);
SQL預設會清除聯合結果中的重複值;
2)每個SELECT語句中的列數須一致;
3)每個SELECT語句包含的表示式與統計函式也必須相同;
4)SELECT語句的順序並不重要,不會改變結果;
5)列的資料型別必須相同或者可以相互轉換;(INT可以轉成VARCHAR,但是反過來不行)
6)看全部的記錄,可以使用UNION ALL運算子,它會返回每個相符的記錄,可以重複;
從聯合建立表:
使用CREATE TABLE AS可以接收來自SELECT查詢的結果並把結果製作成一張表;
任何SELECT查詢的結果都可以用於建立表;
mysql> CREATE TABLE job_titles AS
-> SELECT title FROM job_current
-> UNION
-> SELECT title FROM job_desired
-> UNION
-> SELECT title FROM job_list
-> ORDER BY title;
Query OK, 6 rows affected (0.07 sec)
Records: 6 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM job_titles;
+-----------+
| title |
+-----------+
| EngineerA |
| EngineerH |
| EngineerM |
| EngineerS |
| EngineerW |
| EngineerY |
+-----------+
6 rows in set (0.00 sec)
INTERSECT與EXCEPT:
對應數學集合運算的並集是UNION的話,那麼對應的交集就是INTERSECT,差集是EXCEPT;
使用方式大致和UNION相同;
INTERSECT 會返回同時在第一個和第二個中的列;
EXCEPT 會返回只在第一個查詢 但不在第二個查詢中的列;
現在,我們解決了 聯接 和 聯合;
子查詢與聯接的比較:
幾乎所有的子查詢都可以用聯接來實現;
結果集需要取自多張表的時候,聯接是更好的選擇;
子查詢更擅長聚合值/統計值,使用統計函式等;
我們嘗試把子查詢轉換為連線;
這是子查詢一章中,我們使用過的一個示例:
mysql> SELECT mc.last_name,mc.phone,jc.title
-> FROM my_contacts mc NATURAL JOIN job_current jc
-> WHERE jc.title IN (
-> SELECT title
-> FROM job_list
-> );
+-----------+-------------+-----------+
| last_name | phone | title |
+-----------+-------------+-----------+
| Joy | 15612121212 | EngineerM |
| Mary | 13212121212 | EngineerS |
+-----------+-------------+-----------+
嘗試使用聯接改寫:
由於內聯接比對的列值不唯一,查出的記錄也會有多個;
雖然是改寫,但這個更適合使用子查詢;
mysql> SELECT mc.last_name,mc.phone, jc.title
-> FROM (my_contacts mc NATURAL JOIN job_current jc )
-> INNER JOIN job_list jl
-> ON jc.title = jl.title;
+-----------+-------------+-----------+
| last_name | phone | title |
+-----------+-------------+-----------+
| Joy | 15612121212 | EngineerM |
| Joy | 15612121212 | EngineerM |
| Mary | 13212121212 | EngineerS |
| Mary | 13212121212 | EngineerS |
+-----------+-------------+-----------+
實踐:列出薪資高於平均薪資者的姓名;
mysql> SELECT mc.last_name
-> FROM my_contacts mc NATURAL JOIN job_current jc
-> WHERE jc.salary > (
-> SELECT AVG(salary) FROM job_current
-> );
把自聯接變成子查詢:
我們看一下之前自聯接的SQL;
mysql> SELECT cb1.name clown ,cb2.name boss
-> FROM clown_boss cb1
-> INNER JOIN clown_boss cb2
-> ON cb1.boss_id = cb2.id;
變成子查詢的話,子查詢需要依賴外層查詢的結果,才能取到正確的boss_id,所以子查詢是關聯子查詢:
mysql> SELECT cb1.name,(
-> SELECT name FROM clown_boss
-> WHERE cb1.boss_id = id
-> ) boss
-> FROM clown_boss cb1;
+------+------+
| name | boss |
+------+------+
| B1 | B5 |
| B2 | B1 |
| B3 | B1 |
| B4 | B3 |
| B5 | B5 |
| B6 | B2 |
| B7 | B2 |
+------+------+
總結:
我們學習了外聯接、自聯接與聯合;
聯接轉換為子查詢,反向轉換也OK;
1.自引用外來鍵:SELF-REFERENCING FOREIGN KEY
外來鍵是同一張表的主鍵;
2.左外聯接:LEFT OUTER JOIN
接受左表中的所有記錄,並從右表比對出相符記錄;
3.右外聯接:RIGHT OUTER JOIN
接受右表中的所有記錄,並從左表比對出相符記錄;
4.自聯接:SELF-JOIN
能用一張表做出聯接兩張完全相同表的效果;
5.聯合:UNION與UNION ALL
根據SELECT指定的列合併兩個或多個查詢的結果為一張表;
UNION ALL則可以包含重複的值;
6.CREATE TABLE AS:
使用本命令從任何SELECT語句的結果建立表;
7.INTERSECT:使用這個關鍵字返回同時存在於第一個和第二個查詢中的值;
EXCEPT:使用這個關鍵字返回在第一個查詢中但不在第二個查詢中的值;