SQL 最強大的功能之一是 JOIN 作業,它提供了一種簡捷的方式來將一個資料表中的每一列與另一個資料表中的每一列組合起來。然而,有時我們可能希望從一個資料表中找出另一個資料表中不存在的值。正如我們在今天的文章中所見,JOIN 也可以用於此目的,方法是包含用於連接資料表的述詞,被稱為反聯結(Anti Join)。它們在回答各種商業相關問題時非常有用,例如:
- 哪些客戶沒有下過訂單?
- 哪些員工尚未被分配部門?
- 哪些銷售人員本週沒有成交交易?
本文將使用 PostgreSQL 的 dvdrental 資料庫 dvdrental 資料庫來介紹反聯結的類型以及示範如何編寫它們。我們將在 Navicat Premium Lite 17 中編寫並執行這些查詢。
兩種類型的反聯結
反聯結有兩種類型:
- 左方反聯結:傳回左方資料表中沒有在右方資料表中符合的的列
- 右方反聯結:傳回右方資料表中沒有在左方資料表中符合的的列
傳回的列在下圖中以 藍色 顯示:
接下來的部分將介紹建立反聯結的幾種不同語法,並以左方反聯結為例進行說明。
使用 EXISTS 進行左方反聯結
假設我們想要找出 dvdrental 資料庫中所有未演出任何電影的演員。雖然 SQL 沒有執行此操作的內建語法,但我們可以使用 EXISTS,或更具體地說,使用 NOT EXISTS 來模擬它。以下是該查詢的樣子:
SELECT * FROM actor a WHERE NOT EXISTS ( SELECT * FROM film_actor fa WHERE a.actor_id = fa.actor_id )
如果我們在 Navicat Premium Lite 17 中執行它,我們會得到以下結果:
注意 NOT IN!
由於 EXISTS 和 IN 是相同的,你可能會以為 NOT EXISTS 和 NOT IN 也是相同的,但情況並不總是如此!它們僅在右方資料表(在這個例子中是 film_actor)的外部索引鍵(actor_id)有 NOT NULL 條件約束的情況下才是相同的。
在這個特定的情況下,由於 actor_id 欄位的 NOT NULL 條件約束,NOT IN 查詢會傳回相同的結果:
如果 actor_id 欄位允許 NULL 值,則將傳回空的結果集。我們可以通過以下查詢來驗證這一點:
SELECT * FROM actor WHERE actor_id NOT IN (1, 2, 3, 4, 5, NULL)
上述查詢不返回任何資料列,因為 NULL 在 SQL 中表示一個未知(UNKNOWN)的值。由於我們無法確定 actor_id 是否在包含未知值的值集內,因此整個述詞變為未知!
避免 NOT IN 語法帶來的風險的最簡單方法是使用 NOT EXISTS。其實,實際上甚至不值得去賭 NOT NULL 條件約束的存在,因為資料庫管理員可能會暫時停用該條件約束來載入一些資料,這會使你的查詢在此期間無法使用。
替代語法
如在簡介中提到的,也可以使用 LEFT JOIN 和 RIGHT JOIN 來執行反聯結。為此,你需要加入一個帶有 IS NULL 述詞的 WHERE 子句。以下是該語法的 LEFT JOIN 版本:
SELECT a.* FROM actor a LEFT JOIN film_actor fa ON a.actor_id = fa.actor_id WHERE fa.actor_id IS NULL
請注意,LEFT JOIN 或 RIGHT JOIN 語法可能執行上較慢,因為查詢最佳化器不會將其識別為反聯結作業。
總結
在今天的文章中,我們學習了如何使用三種不同的 SQL 語法來模擬左方反聯結。這三種語法中,NOT EXISTS 應該是你的首選,因為它能最清晰地表達了反聯結的目的,並且往往執行速度最快。
有興趣嘗試 Navicat Premium Lite 17 嗎?你可以下載 14 天的全功能免費試用版 。它適用於 Windows、macOS 和 Linux 作業系統。