ON 說的是 JOIN 左右兩邊的列怎麼配成一個 relation,WHERE 說的是從配好的關係裡篩掉什麼。這兩件事落在求值順序的不同步:FROM 那一步把關係組出來,WHERE 那一步才開始篩。

本篇要反覆用到這兩件事的名字。配對指關係還在組的那個過程——哪一列跟哪一列湊成一列;成品指關係組完之後得到的那一批列——已經湊好、包含補了 NULL 的那些列在內的整批資料。這兩個詞是本篇為了指認這兩個時刻而取的稱呼,SQL 的標準術語裡沒有它們。

內連接底下,條件寫在 ON 與寫在 WHERE 的結果相同,於是它們看起來可以互換。外連接底下不同,而不同的地方正好是外連接存在的理由。條件擺在哪一邊不影響查詢跑不跑得動,只影響外連接剛保護下來的那些列留不留得住。保護指 LEFT JOIN 把左表配不到對象的列也留在結果裡、右表那幾欄補上 NULL。LEFT JOIN 退化成內連接的名單,來源就是把條件放進 WHERE 這一步。

同一個條件放 ON 與放 WHERE 的結果

書店想要一份名單:全部顧客都要列出來,而評價欄只顯示五星的那些。條件寫進 ON:

1SELECT 顧客.姓名, 評價.星等
2FROM 顧客
3LEFT JOIN 訂單 ON 訂單.顧客編號 = 顧客.顧客編號
4LEFT JOIN 評價 ON 評價.訂單編號 = 訂單.訂單編號 AND 評價.星等 = 5;

回來四列:

1佳穎  5
2佳穎  NULL
3宗翰  NULL
4雅文  NULL

三個顧客都在。佳穎出現兩次,因為她有兩張訂單——第一張有五星評價,第二張沒有評價可配,補 NULL。(一位顧客變成兩列是連接在改變列數,本篇只處理條件決定的留不留,連接怎麼改變列數在 SQL.6 連接之後的列數與空缺。)

同一個星等條件改放 WHERE,其餘不動:

1SELECT 顧客.姓名, 評價.星等
2FROM 顧客
3LEFT JOIN 訂單 ON 訂單.顧客編號 = 顧客.顧客編號
4LEFT JOIN 評價 ON 評價.訂單編號 = 訂單.訂單編號
5WHERE 評價.星等 = 5;
6-- 佳穎  5

宗翰、雅文與佳穎的第二張訂單全部消失,結果與內連接相同。書店要的是全部顧客,所以這一版的答案錯了,而查詢照樣跑、不報錯——這一類錯的共同性質在 SQL.13 查詢的合法性與答案的正確性:引擎檢查的範圍、答案錯掉的成因與查證方法。需求若恰好只要有五星評價的那些,答案就對了,剩下的是文字的問題:LEFT 仍然寫在那裡,照字面讀的人會以為沒配到的顧客都在,那是 SQL.20 關鍵字的宣告與引擎的行為:宣告落空的形態與查證 的主題。

條件放 ON 與放 WHERE 在外連接底下分岔的原因

LEFT JOIN 的定義是左邊每一列都留著,右邊配不上的補 NULL。條件寫在 ON 裡的時候,它參與的是「配不配得上」這個判斷——宗翰沒有訂單所以配不上任何評價,佳穎的第二張訂單配不上五星評價,照定義都補 NULL 留下來。

條件寫在 WHERE 裡的時候,關係已經組完了,補上的那些 NULL 也已經在裡面。這時候問「星等等於 5」,那些補 NULL 的列答不出 5,於是被篩掉。外連接剛剛保護下來的列,WHERE 這一步又把它們拿掉了。

內連接底下看不出差別,是因為它本來就不保留配不上的列。沒有被保護的列可以被抵銷,所以兩種寫法自然同值。

開頭那段顧客接訂單、再接評價的查詢有三張表,第二個 LEFT JOIN 保護的「左邊」因此是顧客與訂單累積出來的結果;鏈越長,保護的範圍怎麼跟著累積走,是 SQL.4 鏈式連接的左運算元 的題目。

保護存不存活的測試:把補了 NULL 的那一列代進條件

WHERE 只留下判成真的列。而條件的答案有三種——真、假、以及未知:拿 NULL 去跟任何值比較,答案既不是真也不是假(三值邏輯的推導,以及它讓 NOT IN 整段失效的形態,在 SQL.6 連接之後的列數與空缺:列數膨脹、外連接補的 NULL 與三值邏輯)。

外連接補上的那些列,右表欄位全是 NULL,所以問題變成一句話:這個條件對「什麼都沒有」這一列給出什麼答案。 真的留下,假與未知都被丟掉。

條件對補 NULL 的那一列保護
評價.星等 = 5NULL 比不出來,判成未知取消
評價.星等 IS NOT NULL判成假取消
評價.星等 IS NULL判成真保住
評價.星等 IS NULL OR 評價.星等 = 5前半判成真,整串就是真保住
COALESCE(評價.星等, 0) >= 0NULL 先被換成 0,判成真保住

能不能保住不看用了哪個運算子。 IS NOT NULL 看起來與 IS NULL 成對,而在表上的「保護」欄一個是取消、一個是保住,正是因為它對空缺判成假;而 OR 與 COALESCE 這兩種寫法把未知轉成真,保護就留住了。

這張表上的條件全部提到右表,而左表的條件進不了這張表。 補 NULL 的那一列上左表欄位仍然是原本的值,拿它去問什麼都判得出真假,所以保護一定保得住——這個測試對左表條件恆為真,什麼都判別不出來。左表條件實際的問題出在寫進 ON 的時候:它篩不掉左表的任何一列,還會讓本來配得上的那些配對判假。同一個條件 顧客.姓名 <> '佳穎' 放在三個位置:

 1-- 放 WHERE:佳穎被篩掉,宗翰與雅文的保護完好
 2SELECT 顧客.姓名, 訂單.訂單編號, 評價.星等
 3FROM 顧客
 4LEFT JOIN 訂單 ON 訂單.顧客編號 = 顧客.顧客編號
 5LEFT JOIN 評價 ON 評價.訂單編號 = 訂單.訂單編號 AND 評價.星等 = 5
 6WHERE 顧客.姓名 <> '佳穎';
 7-- 宗翰  NULL  NULL
 8-- 雅文  NULL  NULL
 9
10-- 放進顧客接訂單的 ON:佳穎與她的兩張訂單判假、配不上,照 LEFT 的定義補 NULL 留下
11SELECT 顧客.姓名, 訂單.訂單編號, 評價.星等
12FROM 顧客
13LEFT JOIN 訂單 ON 訂單.顧客編號 = 顧客.顧客編號 AND 顧客.姓名 <> '佳穎'
14LEFT JOIN 評價 ON 評價.訂單編號 = 訂單.訂單編號 AND 評價.星等 = 5;
15-- 佳穎  NULL  NULL     三位顧客全回來,佳穎的兩張訂單一起消失
16-- 宗翰  NULL  NULL
17-- 雅文  NULL  NULL
18
19-- 放進接評價的 ON:顧客與訂單在上一步已經配好,佳穎的兩張訂單留著,只是配不到評價
20SELECT 顧客.姓名, 訂單.訂單編號, 評價.星等
21FROM 顧客
22LEFT JOIN 訂單 ON 訂單.顧客編號 = 顧客.顧客編號
23LEFT JOIN 評價 ON 評價.訂單編號 = 訂單.訂單編號 AND 評價.星等 = 5 AND 顧客.姓名 <> '佳穎';
24-- 佳穎  101   NULL
25-- 佳穎  102   NULL
26-- 宗翰  NULL  NULL
27-- 雅文  NULL  NULL

所以左表條件根本不需要走這個測試,它一開始就屬於 WHERE。

這個測試回答的是保護存不存活,不回答條件該放哪一邊。 歸屬要另外問一句:這個條件描述的是配對,還是成品。「只跟五星的評價配」描述的是配對,屬於 ON;「最後的名單裡只要五星那幾列」描述的是成品,屬於 WHERE。兩句話說的是不同的事,而內連接讓這兩種寫法碰巧回同一批列。

多數形態上「保護存不存活」與「條件該放哪一邊」這兩個問題同向——取消了保護的條件,通常正是因為它在描述成品。IS NULL 是這兩個問題答案不同向的那一個形態:它保住了保護,而它要的正好是那些被保護下來的列,所以它同時是「保護存活」與「屬於 WHERE」。下一節寫的就是這個形態。

找沒配到的那些列:IS NULL 條件的歸屬

有時候要的正是「沒配到的那些」。書店想知道哪些顧客一張單都沒下過:

1SELECT 顧客.姓名
2FROM 顧客 LEFT JOIN 訂單 ON 訂單.顧客編號 = 顧客.顧客編號
3WHERE 訂單.訂單編號 IS NULL;
4-- 宗翰、雅文

這個條件描述的是成品,所以它屬於 WHERE。它與上面那個「退化成內連接」的形態看起來很像——同樣是在 WHERE 裡引用右表欄位——而要的結果剛好相反:這一個挑的正是外連接補上 NULL 的那些列。

分辨這兩種形態的仍然是那個問題:這個條件描述的是配對,還是成品。 「一張單都沒下過」是關於成品的敘述,所以它在 WHERE 是對的位置。

同一題「找沒消費過的顧客」換一個切面,走得到同一個結論。SQL.9 分組鍵的選擇:鍵與一組所代表的對象 從「沒消費的人不會出現在訂單表」這個業務事實走到 NOT EXISTS,並說明在外連接之後分組、再數出零的那種寫法,那個零是查詢自己製造再讀回去的產物。