1. 程式人生 > >深入淺出SQL(13)-外聯接、自聯接與聯合

深入淺出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:使用這個關鍵字返回在第一個查詢中但不在第二個查詢中的值;