Logo

新人日誌

首頁關於我部落格

新人日誌

Logo

網站會不定期發佈技術筆記、職場心得相關的內容,歡迎關注本站!

網站
首頁關於我部落格
部落格
分類系列文

© 新人日誌. All rights reserved. 2020-present.

本文為「SQL 資料庫新手村」系列第 52 篇

跨表單查詢:用不等於(<>)當 JOIN 的 ON 條件

最後更新:2026年2月4日資料庫

前幾篇的 JOIN,合併條件都是用 = 來配對,像是 ON 組別.組別ID = 組員.組別ID。

合併條件的本質就是一種篩選。

篩選的意思是:拿一筆資料裡某個欄位的值,去跟另一個值做比對。

例如 ON 組別.組別ID = 組員.組別ID,就是拿組別表的組別ID,去跟組員表的組別ID 比對。

假設組別表有前端一組(組別ID=1)和前端二組(組別ID=2),組員有 Alice(組別ID=1)和 Carol(組別ID=2)。

JOIN 會先把所有可能的配對列出來,再逐筆比對:

組別.組別ID組別.名稱組員.名稱組員.組別ID比對結果
1前端一組Alice11 = 1✓ 保留
1前端一組Carol21 = 2✗ 捨棄
2前端二組Alice12 = 1✗ 捨棄
2前端二組Carol22 = 2✓ 保留
組別.名稱前端一組
組員.名稱Alice
組員.組別ID1
比對1 = 1
結果✓ 保留
組別.名稱前端一組
組員.名稱Carol
組員.組別ID2
比對1 = 2
結果✗ 捨棄
組別.名稱前端二組
組員.名稱Alice
組員.組別ID1
比對2 = 1
結果✗ 捨棄
組別.名稱前端二組
組員.名稱Carol
組員.組別ID2
比對2 = 2
結果✓ 保留

如果兩邊的值一樣,比對通過(回傳 true),這筆配對就保留。

如果兩邊的值不一樣,比對不通過(回傳 false),這筆配對就捨棄。

我們前面都只用 = 來做比對,也就是「兩邊相等才保留」。

但比對方式不是只有等於,還有不等於(<>)、大於(>)、小於(<)等。

這篇用兩個練習來說明:不同的比對方式會產生什麼效果,以及什麼時候會出問題。

用 <> 配對不同領域的組員

老闆說:「我現在要做一個跨領域的專案,不希望團隊裡面全是同一個領域的人。」

「你幫我列一張清單,把每個組別能配對的『不同領域的組員』都列出來,我再從裡面挑人。」

這次沿用上一篇的四張表:

表欄位
組別組別ID、名稱、領域
組員組員ID、名稱、組別ID
技術技術ID、名稱、領域
人員技術組員ID、技術ID
欄位組別ID、名稱、領域
欄位組員ID、名稱、組別ID
欄位技術ID、名稱、領域
欄位組員ID、技術ID

注意,「領域」這個欄位出現在兩張表上:組別表有領域,技術表也有領域。

老闆說的「不同領域」,指的是組別的領域,不是技術的領域。

換句話說,老闆要的是:把每個組別,配上「來自不同領域」的「其他組別」組員。

例如 Web 領域的組別,只配機器學習或 App 領域的組員,不配同樣是 Web 領域的組員。

這裡的「領域不同」,比的是兩個不同組別的領域:

  • 一邊是「目標組別的領域」(例如前端一組,領域是 Web)
  • 另一邊是「這個組員原本所屬組別的領域」(例如 Dave 屬於 AI 團隊,領域是機器學習)

前端一組(組別領域為 Web)配上 AI 團隊的 Dave(組別領域為機器學習)→ 領域不同 → 保留。

前端一組(組別領域為 Web)配上前端二組的 Carol(組別領域為 Web)→ 領域相同 → 排掉。

組別表:

組別ID名稱領域
1前端一組Web
2前端二組Web
3AI 團隊機器學習
4App 團隊App
名稱前端一組
領域Web
名稱前端二組
領域Web
名稱AI 團隊
領域機器學習
名稱App 團隊
領域App

組員表:

組員ID名稱組別ID
1Alice1
2Bob1
3Carol2
4Dave3
5Eve4
名稱Alice
組別ID1
名稱Bob
組別ID1
名稱Carol
組別ID2
名稱Dave
組別ID3
名稱Eve
組別ID4

老闆想看到的結果像這樣:

組別名稱組別領域組員名稱組員領域
前端一組WebDave機器學習
前端一組WebEveApp
前端二組WebDave機器學習
前端二組WebEveApp
AI 團隊機器學習AliceWeb
AI 團隊機器學習BobWeb
AI 團隊機器學習CarolWeb
AI 團隊機器學習EveApp
App 團隊AppAliceWeb
App 團隊AppBobWeb
App 團隊AppCarolWeb
App 團隊AppDave機器學習
組別領域Web
組員名稱Dave
組員領域機器學習
組別領域Web
組員名稱Eve
組員領域App
組別領域Web
組員名稱Dave
組員領域機器學習
組別領域Web
組員名稱Eve
組員領域App
組別領域機器學習
組員名稱Alice
組員領域Web
組別領域機器學習
組員名稱Bob
組員領域Web
組別領域機器學習
組員名稱Carol
組員領域Web
組別領域機器學習
組員名稱Eve
組員領域App
組別領域App
組員名稱Alice
組員領域Web
組別領域App
組員名稱Bob
組員領域Web
組別領域App
組員名稱Carol
組員領域Web
組別領域App
組員名稱Dave
組員領域機器學習

每個組別都列出來,旁邊配上「不同領域」的組員。

例如前端一組是 Web,所以只配到 Dave(機器學習)和 Eve(App),同樣是 Web 的 Alice、Bob、Carol 都不會出現。

分析老闆的需求

要做這個比對,我們需要知道每個組員原本所屬組別的領域。

每個組別的領域,組別表上就有。

但組員表上沒有「領域」欄位,只有組別ID。

所以第一步,要先透過組別ID 把組員的領域查出來。

第一步:拼出每個組員的領域

組員表上沒有「領域」這個欄位,但組別表有。

把組員和組別合併,就能知道每個組員屬於哪個領域:

SELECT 組員.名稱 AS 組員名稱, 組別.領域 AS 組員領域
FROM 組員
LEFT JOIN 組別 ON 組員.組別ID = 組別.組別ID

結果:

組員名稱組員領域
AliceWeb
BobWeb
CarolWeb
Dave機器學習
EveApp
組員領域Web
組員領域Web
組員領域Web
組員領域機器學習
組員領域App

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 組別.領域 <> 組員領域表.組員領域

結果:

組別名稱組別領域組員名稱組員領域
前端一組WebDave機器學習
前端一組WebEveApp
前端二組WebDave機器學習
前端二組WebEveApp
AI 團隊機器學習AliceWeb
AI 團隊機器學習BobWeb
AI 團隊機器學習CarolWeb
AI 團隊機器學習EveApp
App 團隊AppAliceWeb
App 團隊AppBobWeb
App 團隊AppCarolWeb
App 團隊AppDave機器學習
組別領域Web
組員名稱Dave
組員領域機器學習
組別領域Web
組員名稱Eve
組員領域App
組別領域Web
組員名稱Dave
組員領域機器學習
組別領域Web
組員名稱Eve
組員領域App
組別領域機器學習
組員名稱Alice
組員領域Web
組別領域機器學習
組員名稱Bob
組員領域Web
組別領域機器學習
組員名稱Carol
組員領域Web
組別領域機器學習
組員名稱Eve
組員領域App
組別領域App
組員名稱Alice
組員領域Web
組別領域App
組員名稱Bob
組員領域Web
組別領域App
組員名稱Carol
組員領域Web
組別領域App
組員名稱Dave
組員領域機器學習

前端一組是 Web 領域,所以配對到的是 Dave(機器學習)和 Eve(App)。

同領域的 Alice、Bob、Carol 都被排掉了。

AI 團隊是機器學習領域,配對到的是所有非機器學習的組員:Alice、Bob、Carol(Web)和 Eve(App)。

為什麼可以用 <>?

回想一下,JOIN 的運作方式其實是:

  1. 先把兩張表的每一筆做排列組合(所有可能的配對)
  2. 再用 ON 條件篩選要保留哪些配對

當你用 = 的時候,是從所有配對中挑出「相符的」。

當你用 <> 的時候,是從所有配對中挑出「不相符的」。

所以 <> 的效果就是:把相同領域的配對排掉,留下不同領域的組合。

<> 的限制:多對多關係排不乾淨

上面的例子很順利,但 <> 不是萬能的。

老闆又來了:「我發現有些組員都沒在學新東西。你幫我看一下,每個組員在自己的領域裡面,還有哪些技術是不會的。」

換句話說:

  1. 找出每個組員所屬的組別領域
  2. 列出該領域的所有技術
  3. 把組員已經會的技術排掉,留下還不會的

這次需要用到組員表、技術表和人員技術表:

組員表:

組員ID名稱組別ID
1Alice1
2Bob1
3Carol2
4Dave3
5Eve4
名稱Alice
組別ID1
名稱Bob
組別ID1
名稱Carol
組別ID2
名稱Dave
組別ID3
名稱Eve
組別ID4

技術表:

技術ID名稱領域
1TypeScriptWeb
2ReactWeb
3Python機器學習
4SwiftApp
名稱TypeScript
領域Web
名稱React
領域Web
名稱Python
領域機器學習
名稱Swift
領域App

人員技術表:

組員ID技術ID
11
12
22
32
43
54
技術ID1
技術ID2
技術ID2
技術ID2
技術ID3
技術ID4

Alice(組員ID=1)會 TypeScript 和 React,也就是 Web 領域的技術她全部都會。

Bob(組員ID=2)只會 React,還不會 TypeScript。

所以像 Bob 不會 TypeScript 這種情況,就是老闆想要找出來的。

試試看用 <> 來排除

直覺想法:用 <> 把已經會的技術排掉,剩下的就是不會的。

我們拿 Alice 來試試看。

Alice 在人員技術表裡有兩筆資料:

組員ID技術ID
11
12
技術ID1
技術ID2

Web 領域的技術有兩個:TypeScript(技術ID=1)和 React(技術ID=2)。

如果用 <> 來排除,我們要合併人員技術表和技術表。

一步一步來看。

合併人員技術表和技術表

FROM 人員技術
LEFT JOIN 技術 ON 人員技術.技術ID <> 技術.技術ID

ON 條件是 人員技術.技術ID <> 技術.技術ID,意思是「人員技術表的技術ID 不等於技術表的技術ID 的才配對」。

結果(共 18 筆,這裡只列出部分):

組員ID技術ID技術表的技術ID技術表的名稱技術表的領域
112ReactWeb
113Python機器學習
114SwiftApp
121TypeScriptWeb
123Python機器學習
124SwiftApp
221TypeScriptWeb
223Python機器學習
224SwiftApp
⋮⋮⋮⋮⋮
技術ID1
技術表的技術ID2
技術表的名稱React
技術表的領域Web
技術ID1
技術表的技術ID3
技術表的名稱Python
技術表的領域機器學習
技術ID1
技術表的技術ID4
技術表的名稱Swift
技術表的領域App
技術ID2
技術表的技術ID1
技術表的名稱TypeScript
技術表的領域Web
技術ID2
技術表的技術ID3
技術表的名稱Python
技術表的領域機器學習
技術ID2
技術表的技術ID4
技術表的名稱Swift
技術表的領域App
技術ID2
技術表的技術ID1
技術表的名稱TypeScript
技術表的領域Web
技術ID2
技術表的技術ID3
技術表的名稱Python
技術表的領域機器學習
技術ID2
技術表的技術ID4
技術表的名稱Swift
技術表的領域App
技術ID⋮
技術表的技術ID⋮
技術表的名稱⋮
技術表的領域⋮

每個人的每筆技術,都配上了所有「技術ID 不同」的技術。資料太多了,先縮小範圍。

篩選 Alice 的資料

FROM 人員技術
LEFT JOIN 技術 ON 人員技術.技術ID <> 技術.技術ID
WHERE 人員技術.組員ID = 1

加上 WHERE 人員技術.組員ID = 1,只看 Alice(共 6 筆):

組員ID技術ID技術表的技術ID技術表的名稱技術表的領域
112ReactWeb
113Python機器學習
114SwiftApp
121TypeScriptWeb
123Python機器學習
124SwiftApp
技術ID1
技術表的技術ID2
技術表的名稱React
技術表的領域Web
技術ID1
技術表的技術ID3
技術表的名稱Python
技術表的領域機器學習
技術ID1
技術表的技術ID4
技術表的名稱Swift
技術表的領域App
技術ID2
技術表的技術ID1
技術表的名稱TypeScript
技術表的領域Web
技術ID2
技術表的技術ID3
技術表的名稱Python
技術表的領域機器學習
技術ID2
技術表的技術ID4
技術表的名稱Swift
技術表的領域App

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 筆):

組員ID技術ID技術表的技術ID技術表的名稱技術表的領域
112ReactWeb
121TypeScriptWeb
技術ID1
技術表的技術ID2
技術表的名稱React
技術表的領域Web
技術ID2
技術表的技術ID1
技術表的名稱TypeScript
技術表的領域Web

加上 SELECT

SELECT 
    人員技術.組員ID,
    人員技術.技術ID, 
    技術.技術ID AS 技術表的技術ID, 
    技術.名稱 AS 技術表的名稱
FROM 人員技術
LEFT JOIN 技術 ON 人員技術.技術ID <> 技術.技術ID
WHERE 人員技術.組員ID = 1 AND 技術.領域 = 'Web'

最終結果:

組員ID技術ID技術表的技術ID技術表的名稱
112React
121TypeScript
技術ID1
技術表的技術ID2
技術表的名稱React
技術ID2
技術表的技術ID1
技術表的名稱TypeScript

結果說 Alice 不會 React,也不會 TypeScript。

但 Alice 明明兩個都會!

為什麼排不掉?

問題在於:Alice 有兩筆技術資料,<> 每次只能比對一筆。

把所有配對的比對過程列出來:

技術ID技術表的技術ID技術表的名稱比對結果
11TypeScript1 ≠ 1✗ 不成立,排掉
12React1 ≠ 2✓ 成立,留下
21TypeScript2 ≠ 1✓ 成立,留下
22React2 ≠ 2✗ 不成立,排掉
技術表的技術ID1
技術表的名稱TypeScript
比對1 ≠ 1
結果✗ 不成立,排掉
技術表的技術ID2
技術表的名稱React
比對1 ≠ 2
結果✓ 成立,留下
技術表的技術ID1
技術表的名稱TypeScript
比對2 ≠ 1
結果✓ 成立,留下
技術表的技術ID2
技術表的名稱React
比對2 ≠ 2
結果✗ 不成立,排掉

第一筆(技術ID=1)排掉了 TypeScript,但留下了 React。

第二筆(技術ID=2)排掉了 React,但留下了 TypeScript。

兩筆各自排掉了一個,但合在一起的結果是:React 和 TypeScript 都還在。

對比 Bob 的情況

Bob 只有一筆技術資料(技術ID=2,React)。

排列組合:

組員ID技術ID技術表的技術ID技術表的名稱
221TypeScript
222React
技術ID2
技術表的技術ID1
技術表的名稱TypeScript
技術ID2
技術表的技術ID2
技術表的名稱React

用 <> 篩選:

組員ID技術ID技術表的技術ID技術表的名稱
221TypeScript
技術ID2
技術表的技術ID1
技術表的名稱TypeScript

Bob 不會 TypeScript,結果是對的。

因為 Bob 只有一筆,<> 一次就能排乾淨。

但 Alice 有兩筆,第一筆排掉的東西,第二筆又帶回來了。

規則:一對一可以,多對多不行

上一個練習之所以成功,是因為每個組員只有一個領域,是一對一的關係。

<> 每次只比對一筆,一對一的時候剛好夠用。

但組員和技術是多對多的關係,一個人可以會很多技術。

<> 每次只排掉一個,其他筆又把同樣的東西帶回來,所以排不乾淨。

這題怎麼解?

JOIN 是「橫向合併」——把不同表的欄位拼在一起。

但這題需要的是「垂直排除」——把某些資料列整列拿掉。

這需要用到其他的 SQL 工具,我們在後面的章節會學到。

其他合併條件:大於和小於

除了 = 和 <>,合併條件也可以用 <、>、<=、>=。

舉個例子:老闆想找出每個會員「上次登入之前發表的文章」。

會員表:

會員ID名稱上次登入
1Alice2025-01-15
名稱Alice
上次登入2025-01-15

文章表:

文章ID標題發表日期
1SQL 入門2025-01-10
2JOIN 教學2025-01-20
標題SQL 入門
發表日期2025-01-10
標題JOIN 教學
發表日期2025-01-20

用 < 來合併:

SELECT 會員.名稱, 文章.標題, 文章.發表日期
FROM 會員
LEFT JOIN 文章 ON 文章.發表日期 < 會員.上次登入

結果:

名稱標題發表日期
AliceSQL 入門2025-01-10
標題SQL 入門
發表日期2025-01-10

「SQL 入門」的發表日期(1/10)早於 Alice 的登入時間(1/15),所以配對成功。

「JOIN 教學」的發表日期(1/20)晚於登入時間,所以不會出現。

合併條件裡放 <,就能找出「時間在某個點之前」的資料。

小結

這篇學到了三件事:

合併條件可以用不同的運算子:

= 找相符的配對,<> 找不相符的配對,< 和 > 找時間或數值的前後關係。

<> 適合一對一關係:

當每筆資料只有一個比對點的時候,<> 可以正確排除。

像是「每個組員只有一個領域」,用 <> 就能順利找出不同領域的配對。

<> 對多對多關係無效:

當一筆資料有多個比對點的時候(像是一個人會多種技術),<> 每次只能排掉一個,其他筆又會把它帶回來。

要處理多對多的排除,需要用到後面章節會學到的「垂直操作」。

上一篇跨表單查詢:用合併鏈串接多張表
下一篇跨表單查詢:JOIN 的執行順序是什麼?
目前還沒有留言,成為第一個留言的人吧!

發表留言

留言將在審核後顯示。

資料庫

目錄

  • 用 <> 配對不同領域的組員
  • 分析老闆的需求
  • 第一步:拼出每個組員的領域
  • 第二步:用 <> 配對不同領域
  • 為什麼可以用 <>?
  • <> 的限制:多對多關係排不乾淨
  • 試試看用 <> 來排除
  • 合併人員技術表和技術表
  • 篩選 Alice 的資料
  • 篩選 Web 領域的技術
  • 加上 SELECT
  • 為什麼排不掉?
  • 對比 Bob 的情況
  • 規則:一對一可以,多對多不行
  • 這題怎麼解?
  • 其他合併條件:大於和小於
  • 小結