iT邦幫忙

2021 iThome 鐵人賽

DAY 9
1
自我挑戰組

後端工程師與圖的修練系列 第 9

資料表集合與文氏圖

數學上,文氏圖常用作集合論的表達工具,在做資料庫查詢的時候,其實也常常使用到這樣的概念,交集、差集、聯集、補集…etc,都是集合代數的運算操作。

簡單的說,文氏圖關注的是集合與集合之間的交集。

集合,在資料庫被當作 Table,而 JOIN 的方式,就是一張表如何連結另外一張表的關係。

在 MySQL 中,JOIN 最常見的方式有:

  • INNER JOIN
  • LEFT JOIN == LEFT OUTER JOIN (相同的東西)
  • RIGHT JOIN == RIGHT OUTER JOIN (相同的東西)
  • FULL OUTER JOIN

本篇也會在介紹幾個特別的 JOIN:

  • CROSS JOIN
  • FULL JOIN = FULL OUTER JOIN
  • SELF JOIN
  • UNION

INNER JOIN, LEFT JOIN, RIGHT JOIN

如果 A 表和 B 表可以有某種關聯,則會把他們的雙方都有的關聯做交集 (Intersection),這個交集後,會出現三個區塊 LEFT JOIN, INNER JOIN, RIGHT JOIN,你可以選擇任一個區塊當作主要的集合,帶出另一個集合,像是下表:

https://ithelp.ithome.com.tw/upload/images/20210919/20092753nSvG9k8CuB.png

不難想像,LEFT JOIN 區塊就是以 LEFT JOIN 存在的元素交集 RIGHT JOIN,此時要是有東西不存在 LEFT JOIN 這區,也就不會帶出來; 這件事對 RIGHT JOIN 區塊來說亦然,可是 INNER JOIN 則是只針對兩邊的交集部分進行陳列,所以只要兩邊沒有交集,資料也不會出現。 (有關聯的資料才會出現的意思)

範例資料表

為了底下舉例方便,我建立了測試的資料表:

-- A: User (使用者表)
CREATE TABLE users (
    id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
    PersonalInfoId INT NOT NULL)

-- B: PersonalInfo (使用者個人資料)
CREATE TABLE PersonalInfo(
    id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
    identity VARCHAR(256),
    address VARCHAR(512)
)

現在,你如果確定一個 user 就一定會有一個 personal info 個人資料的資料表對應,就屬於 1 對 1 的映射關係,你就可以使用 INNER JOIN 來進行查詢,一但 userpersonal info 它不存在各自的表,只要有一個找不到,則這筆資料就不會顯示,必須是兩邊都有才會顯示。 (user 或 personal info 存在缺漏資料時)

現在,對這兩個表填充一下資料

PersonalInfo:

https://ithelp.ithome.com.tw/upload/images/20210919/20092753qpnzqDxigh.png

Users:

https://ithelp.ithome.com.tw/upload/images/20210919/20092753ayCWk1wvO1.png

然後,寫一個 1-1 關係查詢的 JOIN:

SELECT * FROM users u
INNER JOIN PersonalInfo pi ON pi.id = u.PersonalInfoId;

此時因為 2 不存在某一處 (user 2 不在 PersonalInfo),則無法被 INNER JOIN 一對一的找到,所以連顯示都不會,因此,如果要尋找存在性就不可以使用 INNER JOIN,因為你根本找不出兩邊的映射,它也不會顯示**不完整"**的資料,就像下圖。

https://ithelp.ithome.com.tw/upload/images/20210919/20092753vWvkbk0DXy.png

請參考下圖,如果想要完整的列出 左邊的表 users,但 Right 右邊可有可無,或是左右資料量不是 1-1 的大小,則使用 LEFT JOIN,換句話說,如果要以 users 尋找是否存在一個或更多 personal info,需要用 users 去 LEFT JOIN PersonalInfo。

https://ithelp.ithome.com.tw/upload/images/20210919/20092753I2ozx7eKHh.png

比方說,一個使用者有很多訂單,所以要查詢一個使用者的很多筆訂單,就可以使用 LEFT JOIN 來找出多筆。

RIGHTLEFT 的對照,只是左右互換,可以把 SELECT * FROM A LEFT JOIN B, A 跟 B 互換本身就變成 RIGHT JOIN,只是在誰大誰小的問題。

UNION 和 FULL JOIN

如果說,現在希望檢查兩張表的資料,任一邊 A, B 只要有一邊不存在,都 SHOW 出來這個狀況,請參考下表圖例:

PersonalInfo 少 2, 比 user 多一個 5

https://ithelp.ithome.com.tw/upload/images/20210919/20092753y9k9HRdY0x.png

Users 有 2,但少一個 5

https://ithelp.ithome.com.tw/upload/images/20210919/20092753toffOFkFt6.png

此時就可以使用 FULL JOIN (mysql 不支援,可使用 LEFT + RIGHT JOIN 的方法) ,或稱為 UNION。

此方法的不同之處在於兩邊資料表都有缺漏資料,或用另一個觀點看,兩邊資料不對稱,用此方法找出兩邊不對稱的地方。

針對上述資料的問題,則寫一段 SQL 呈現這個狀況:

-- LEFT 表為主的資料去查另一張表 (只差在 LEFT JOIN),這會帶出隔壁沒有的資料
SELECT * FROM users u
LEFT JOIN PersonalInfo pi ON pi.id = u.PersonalInfoId
-- UNION 起來
UNION
-- RIGHT 表為主的資料去查令一張表 (只差在 RIGHT JOIN),這也會帶出隔壁沒有的資料
SELECT * FROM users u
RIGHT JOIN PersonalInfo pi ON pi.id = u.PersonalInfoId

上述 SQL 中都各自帶出了隔壁沒有的資料,就容易看出缺失之處,如下圖結果:

https://ithelp.ithome.com.tw/upload/images/20210919/20092753U8umL5Y7PU.png

所以,整個 FULL JOIN 是全部兩邊集合(不管大小),不是只有中間,如下圖:

https://ithelp.ithome.com.tw/upload/images/20210919/20092753g8ZG0AuT6M.png

備註
顯然,以邏輯來說,Users, PersonalInfo 不應該隨便缺任一筆資料,友善對待資料,請加上 Foreign Key Check,您可以少一些白髮。

CREATE TABLE users (
    id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
    PersonalInfoId INT NOT NULL,
    CONSTRAINT FK_personalInfo FOREIGN KEY(PersonalInfoId) REFERENCES PersonalInfo(id)
)

SELF JOIN

SELF JOIN,表示自己 JOIN 自己,在圖表中是:

https://ithelp.ithome.com.tw/upload/images/20210919/20092753b8T4afpkJK.png

SELF JOIN 要如何示範? 假設 PersonalInfo 的資料中,想要找出同一個地址,且不同人的身分證,見下圖示。

https://ithelp.ithome.com.tw/upload/images/20210919/20092753QFa39VW9Je.png

則可以這麼查詢:

SELECT pi1.identity, pi2.identity, pi1.address 
FROM PersonalInfo pi1, PersonalInfo pi2  -- 一次查兩表
WHERE pi1.address = pi2.address AND pi1.identity <> pi2.identity; -- 處理兩表關係

地址相同,人不同,得到下方的結果,如下圖。

https://ithelp.ithome.com.tw/upload/images/20210919/20092753ComqJ8BjIU.png

笛卡兒乘積與 CROSS JOIN

什麼是笛卡兒乘積 (Cartesian product) ?

最簡單的範例就是 9x9 乘法表,所以先建立一個類似 9x9 乘法表的兩個表:

CREATE TABLE A(
	num INT PRIMARY KEY
);
CREATE TABLE B(
	num INT PRIMARY KEY
);

在 A, B 表分別填入 1~9 (這裡不小心填到 10):

https://ithelp.ithome.com.tw/upload/images/20210919/20092753X5XRX4aoXe.png

建立好資料後,現在要直接對 A, B 做 CROSS JOIN:

SELECT A.num as A, B.num as B, A.num * B.num as 乘積 FROM A
CROSS JOIN B
ORDER BY A.num, B.num;

https://ithelp.ithome.com.tw/upload/images/20210919/20092753DKw0Hitb9g.png

現在,可以清楚知道,CROSS JOIN 就是把兩張表的每一項都 JOIN,而且不指定任何條件,即便 A, B; B, A 交換出現也會 JOIN 每一項,這個也就是笛卡兒乘積 (所有可能的情況)。

備註
使用 CROSS JOIN 產生的笛卡兒乘積絕對會對效能造成很大的負擔。

另外在本篇沒有介紹到的東西是 DISTINCT,如果你在單一張表中會出現多筆重複的資料,想要按照某個欄位只選出一筆,則可以使用 DISTINCT

References:
[1] https://justcode.ikeepstudying.com/2016/08/mysql-%E5%9B%BE%E8%A7%A3-inner-join%E3%80%81left-join%E3%80%81right-join%E3%80%81full-outer-join%E3%80%81union%E3%80%81union-all%E7%9A%84%E5%8C%BA%E5%88%AB/
[2] https://www.chriswirz.com/materialized-views-in-mysql/
[3] https://www.educba.com/joins-in-mysql/
[4] https://www.techagilist.com/mainframe/db2/outer-join-step-by-step-walkthrough-with-examples/
[5] https://stackoverflow.com/questions/13997365/sql-joins-as-venn-diagram
[6] https://www.techonthenet.com/mysql/joins.php
[7] https://www.guru99.com/joins.html


上一篇
狀態流程圖與有限狀態機
下一篇
集合推理與歐拉圖
系列文
後端工程師與圖的修練31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言