前幾篇的 JOIN,合併條件都是用 = 來配對,像是 ON 組別.組別ID = 組員.組別ID。
合併條件的本質就是一種篩選。
篩選的意思是:拿一筆資料裡某個欄位的值,去跟另一個值做比對。
例如 ON 組別.組別ID = 組員.組別ID,就是拿組別表的組別ID,去跟組員表的組別ID 比對。
假設組別表有前端一組(組別ID=1)和前端二組(組別ID=2),組員有 Alice(組別ID=1)和 Carol(組別ID=2)。
JOIN 會先把所有可能的配對列出來,再逐筆比對:
如果兩邊的值一樣,比對通過(回傳 true),這筆配對就保留。
如果兩邊的值不一樣,比對不通過(回傳 false),這筆配對就捨棄。
我們前面都只用 = 來做比對,也就是「兩邊相等才保留」。
但比對方式不是只有等於,還有不等於(<>)、大於(>)、小於(<)等。
這篇用兩個練習來說明:不同的比對方式會產生什麼效果,以及什麼時候會出問題。
用 <> 配對不同領域的組員
老闆說:「我現在要做一個跨領域的專案,不希望團隊裡面全是同一個領域的人。」
「你幫我列一張清單,把每個組別能配對的『不同領域的組員』都列出來,我再從裡面挑人。」
這次沿用上一篇的四張表:
注意,「領域」這個欄位出現在兩張表上:組別表有領域,技術表也有領域。
老闆說的「不同領域」,指的是組別的領域,不是技術的領域。
換句話說,老闆要的是:把每個組別,配上「來自不同領域」的「其他組別」組員。
例如 Web 領域的組別,只配機器學習或 App 領域的組員,不配同樣是 Web 領域的組員。
這裡的「領域不同」,比的是兩個不同組別的領域:
- 一邊是「目標組別的領域」(例如前端一組,領域是 Web)
- 另一邊是「這個組員原本所屬組別的領域」(例如 Dave 屬於 AI 團隊,領域是機器學習)
前端一組(組別領域為 Web)配上 AI 團隊的 Dave(組別領域為機器學習)→ 領域不同 → 保留。
前端一組(組別領域為 Web)配上前端二組的 Carol(組別領域為 Web)→ 領域相同 → 排掉。
組別表:
組員表:
老闆想看到的結果像這樣:
每個組別都列出來,旁邊配上「不同領域」的組員。
例如前端一組是 Web,所以只配到 Dave(機器學習)和 Eve(App),同樣是 Web 的 Alice、Bob、Carol 都不會出現。
分析老闆的需求
要做這個比對,我們需要知道每個組員原本所屬組別的領域。
每個組別的領域,組別表上就有。
但組員表上沒有「領域」欄位,只有組別ID。
所以第一步,要先透過組別ID 把組員的領域查出來。
第一步:拼出每個組員的領域
組員表上沒有「領域」這個欄位,但組別表有。
把組員和組別合併,就能知道每個組員屬於哪個領域:
SELECT 組員.名稱 AS 組員名稱, 組別.領域 AS 組員領域
FROM 組員
LEFT JOIN 組別 ON 組員.組別ID = 組別.組別ID結果:
Alice、Bob、Carol 都是 Web 領域,Dave 是機器學習,Eve 是 App。
第二步:用 <> 配對不同領域
現在要做的是:把每個組別和「不同領域的組員」配在一起。
關鍵在 ON 條件:不是用 =,而是用 <>。
<> 是 SQL 裡「不等於」的意思(跟 != 一樣)。
SELECT
組別.名稱 AS 組別名稱,
組別.領域 AS 組別領域,
組員領域表.組員名稱,
組員領域表.組員領域
FROM 組別
LEFT JOIN (
SELECT 組員.名稱 AS 組員名稱, 組別.領域 AS 組員領域
FROM 組員
LEFT JOIN 組別 ON 組員.組別ID = 組別.組別ID
) AS 組員領域表 ON 組別.領域 <> 組員領域表.組員領域結果:
前端一組是 Web 領域,所以配對到的是 Dave(機器學習)和 Eve(App)。
同領域的 Alice、Bob、Carol 都被排掉了。
AI 團隊是機器學習領域,配對到的是所有非機器學習的組員:Alice、Bob、Carol(Web)和 Eve(App)。
為什麼可以用 <>?
回想一下,JOIN 的運作方式其實是:
- 先把兩張表的每一筆做排列組合(所有可能的配對)
- 再用 ON 條件篩選要保留哪些配對
當你用 = 的時候,是從所有配對中挑出「相符的」。
當你用 <> 的時候,是從所有配對中挑出「不相符的」。
所以 <> 的效果就是:把相同領域的配對排掉,留下不同領域的組合。
<> 的限制:多對多關係排不乾淨
上面的例子很順利,但 <> 不是萬能的。
老闆又來了:「我發現有些組員都沒在學新東西。你幫我看一下,每個組員在自己的領域裡面,還有哪些技術是不會的。」
換句話說:
- 找出每個組員所屬的組別領域
- 列出該領域的所有技術
- 把組員已經會的技術排掉,留下還不會的
這次需要用到組員表、技術表和人員技術表:
組員表:
技術表:
人員技術表:
Alice(組員ID=1)會 TypeScript 和 React,也就是 Web 領域的技術她全部都會。
Bob(組員ID=2)只會 React,還不會 TypeScript。
所以像 Bob 不會 TypeScript 這種情況,就是老闆想要找出來的。
試試看用 <> 來排除
直覺想法:用 <> 把已經會的技術排掉,剩下的就是不會的。
我們拿 Alice 來試試看。
Alice 在人員技術表裡有兩筆資料:
Web 領域的技術有兩個:TypeScript(技術ID=1)和 React(技術ID=2)。
如果用 <> 來排除,我們要合併人員技術表和技術表。
一步一步來看。
合併人員技術表和技術表
FROM 人員技術
LEFT JOIN 技術 ON 人員技術.技術ID <> 技術.技術IDON 條件是 人員技術.技術ID <> 技術.技術ID,意思是「人員技術表的技術ID 不等於技術表的技術ID 的才配對」。
結果(共 18 筆,這裡只列出部分):
每個人的每筆技術,都配上了所有「技術ID 不同」的技術。資料太多了,先縮小範圍。
篩選 Alice 的資料
FROM 人員技術
LEFT JOIN 技術 ON 人員技術.技術ID <> 技術.技術ID
WHERE 人員技術.組員ID = 1加上 WHERE 人員技術.組員ID = 1,只看 Alice(共 6 筆):
Alice 會的技術(技術ID=1 和 2),各自配上了所有「技術ID 不同」的技術。
但 Alice 屬於前端一組,組別領域是 Web,所以我們只想看 Web 領域的技術,不需要 Python 和 Swift。
篩選 Web 領域的技術
FROM 人員技術
LEFT JOIN 技術 ON 人員技術.技術ID <> 技術.技術ID
WHERE 人員技術.組員ID = 1 AND 技術.領域 = 'Web'加上 AND 技術.領域 = 'Web',只保留 Web 領域的技術(剩 2 筆):
加上 SELECT
SELECT
人員技術.組員ID,
人員技術.技術ID,
技術.技術ID AS 技術表的技術ID,
技術.名稱 AS 技術表的名稱
FROM 人員技術
LEFT JOIN 技術 ON 人員技術.技術ID <> 技術.技術ID
WHERE 人員技術.組員ID = 1 AND 技術.領域 = 'Web'最終結果:
結果說 Alice 不會 React,也不會 TypeScript。
但 Alice 明明兩個都會!
為什麼排不掉?
問題在於:Alice 有兩筆技術資料,<> 每次只能比對一筆。
把所有配對的比對過程列出來:
第一筆(技術ID=1)排掉了 TypeScript,但留下了 React。
第二筆(技術ID=2)排掉了 React,但留下了 TypeScript。
兩筆各自排掉了一個,但合在一起的結果是:React 和 TypeScript 都還在。
對比 Bob 的情況
Bob 只有一筆技術資料(技術ID=2,React)。
排列組合:
用 <> 篩選:
Bob 不會 TypeScript,結果是對的。
因為 Bob 只有一筆,<> 一次就能排乾淨。
但 Alice 有兩筆,第一筆排掉的東西,第二筆又帶回來了。
規則:一對一可以,多對多不行
上一個練習之所以成功,是因為每個組員只有一個領域,是一對一的關係。
<> 每次只比對一筆,一對一的時候剛好夠用。
但組員和技術是多對多的關係,一個人可以會很多技術。
<> 每次只排掉一個,其他筆又把同樣的東西帶回來,所以排不乾淨。
這題怎麼解?
JOIN 是「橫向合併」——把不同表的欄位拼在一起。
但這題需要的是「垂直排除」——把某些資料列整列拿掉。
這需要用到其他的 SQL 工具,我們在後面的章節會學到。
其他合併條件:大於和小於
除了 = 和 <>,合併條件也可以用 <、>、<=、>=。
舉個例子:老闆想找出每個會員「上次登入之前發表的文章」。
會員表:
文章表:
用 < 來合併:
SELECT 會員.名稱, 文章.標題, 文章.發表日期
FROM 會員
LEFT JOIN 文章 ON 文章.發表日期 < 會員.上次登入結果:
「SQL 入門」的發表日期(1/10)早於 Alice 的登入時間(1/15),所以配對成功。
「JOIN 教學」的發表日期(1/20)晚於登入時間,所以不會出現。
合併條件裡放 <,就能找出「時間在某個點之前」的資料。
小結
這篇學到了三件事:
合併條件可以用不同的運算子:
= 找相符的配對,<> 找不相符的配對,< 和 > 找時間或數值的前後關係。
<> 適合一對一關係:
當每筆資料只有一個比對點的時候,<> 可以正確排除。
像是「每個組員只有一個領域」,用 <> 就能順利找出不同領域的配對。
<> 對多對多關係無效:
當一筆資料有多個比對點的時候(像是一個人會多種技術),<> 每次只能排掉一個,其他筆又會把它帶回來。
要處理多對多的排除,需要用到後面章節會學到的「垂直操作」。