<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Where-Clause on Tarragon</title><link>https://tarrragon.github.io/blog/tags/where-clause/</link><description>Recent content in Where-Clause on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 31 Aug 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/where-clause/index.xml" rel="self" type="application/rss+xml"/><item><title>SQL.5 ON 與 WHERE：連接條件與篩選條件各自的職責</title><link>https://tarrragon.github.io/blog/backend/01-database/sql/on-describes-where-filters/</link><pubDate>Mon, 31 Aug 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/sql/on-describes-where-filters/</guid><description>&lt;p>&lt;code>ON&lt;/code> 說的是 &lt;code>JOIN&lt;/code> 左右兩邊的列怎麼配成一個 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/knowledge-cards/relation/" data-link-title="Relation（關係）" data-link-desc="看到 SQL 教材說「組成一個關係」而不確定它指的是不是資料表時">relation&lt;/a>，&lt;code>WHERE&lt;/code> 說的是從配好的關係裡篩掉什麼。這兩件事落在求值順序的不同步：&lt;code>FROM&lt;/code> 那一步把關係組出來，&lt;code>WHERE&lt;/code> 那一步才開始篩。&lt;/p>
&lt;p>本篇要反覆用到這兩件事的名字。&lt;strong>配對&lt;/strong>指關係還在組的那個過程——哪一列跟哪一列湊成一列；&lt;strong>成品&lt;/strong>指關係組完之後得到的那一批列——已經湊好、包含補了 NULL 的那些列在內的整批資料。這兩個詞是本篇為了指認這兩個時刻而取的稱呼，SQL 的標準術語裡沒有它們。&lt;/p>
&lt;p>內連接底下，條件寫在 &lt;code>ON&lt;/code> 與寫在 &lt;code>WHERE&lt;/code> 的結果相同，於是它們看起來可以互換。&lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/knowledge-cards/outer-join/" data-link-title="Outer Join（外連接）" data-link-desc="想確認 LEFT / RIGHT / FULL 各自保護哪一側、或連接之後有些人從名單上消失時">外連接&lt;/a>底下不同，而不同的地方正好是外連接存在的理由。條件擺在哪一邊不影響查詢跑不跑得動，只影響外連接剛保護下來的那些列留不留得住。保護指 &lt;code>LEFT JOIN&lt;/code> 把左表配不到對象的列也留在結果裡、右表那幾欄補上 &lt;code>NULL&lt;/code>。&lt;code>LEFT JOIN&lt;/code> 退化成內連接的名單，來源就是把條件放進 &lt;code>WHERE&lt;/code> 這一步。&lt;/p>
&lt;h2 id="同一個條件放-on-與放-where-的結果">同一個條件放 ON 與放 WHERE 的結果&lt;/h2>
&lt;p>書店想要一份名單：全部顧客都要列出來，而評價欄只顯示五星的那些。條件寫進 &lt;code>ON&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">顧客&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">姓名&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">評價&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">星等&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">顧客&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LEFT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">JOIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">訂單&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">訂單&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">顧客編號&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">顧客&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">顧客編號&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LEFT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">JOIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">評價&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">評價&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">訂單編號&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">訂單&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">訂單編號&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AND&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">評價&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">星等&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>回來四列：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">佳穎 5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">佳穎 NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">宗翰 NULL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">雅文 NULL&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>三個顧客都在。佳穎出現兩次，因為她有兩張訂單——第一張有五星評價，第二張沒有評價可配，補 NULL。（一位顧客變成兩列是連接在改變列數，本篇只處理條件決定的留不留，連接怎麼改變列數在 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/join-changes-rows-and-nulls/" data-link-title="SQL.6 連接之後的列數與空缺：列數膨脹、外連接補的 NULL 與三值邏輯" data-link-desc="連接讓聚合多算的機制與計數、加總、平均、中位數各自的修法，以及外連接補出來的 NULL 在 count、等號比較、NOT IN 與 NOT EXISTS 上的行為">SQL.6 連接之後的列數與空缺&lt;/a>。）&lt;/p>
&lt;p>同一個星等條件改放 &lt;code>WHERE&lt;/code>，其餘不動：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">顧客&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">姓名&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">評價&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">星等&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">顧客&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LEFT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">JOIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">訂單&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">訂單&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">顧客編號&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">顧客&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">顧客編號&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">LEFT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">JOIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">評價&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">評價&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">訂單編號&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">訂單&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">訂單編號&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">評價&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="err">星等&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- 佳穎 5&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>宗翰、雅文與佳穎的第二張訂單全部消失，結果與內連接相同。書店要的是全部顧客，所以這一版的答案錯了，而查詢照樣跑、不報錯——這一類錯的共同性質在 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/well-formed-is-not-correct/" data-link-title="SQL.13 查詢的合法性與答案的正確性：引擎檢查的範圍、答案錯掉的成因與查證方法" data-link-desc="合法與問對了分別由誰負責、答案錯掉時引擎為什麼不報錯，以及靜態分析、約束與反例資料各自判得出哪些錯">SQL.13 查詢的合法性與答案的正確性：引擎檢查的範圍、答案錯掉的成因與查證方法&lt;/a>。需求若恰好只要有五星評價的那些，答案就對了，剩下的是文字的問題：&lt;code>LEFT&lt;/code> 仍然寫在那裡，照字面讀的人會以為沒配到的顧客都在，那是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/declared-intent-vs-behaviour/" data-link-title="SQL.20 關鍵字的宣告與引擎的行為：宣告落空的形態與查證" data-link-desc="LEFT JOIN、CROSS JOIN、DISTINCT、COALESCE 與表的別名各自對讀的人宣告了什麼、宣告落空時下一個修改的人付出什麼代價，以及把關鍵字換成最弱的版本再跑一次的查證動作">SQL.20 關鍵字的宣告與引擎的行為：宣告落空的形態與查證&lt;/a> 的主題。&lt;/p>
&lt;h2 id="條件放-on-與放-where-在外連接底下分岔的原因">條件放 ON 與放 WHERE 在外連接底下分岔的原因&lt;/h2>
&lt;p>&lt;code>LEFT JOIN&lt;/code> 的定義是左邊每一列都留著，右邊配不上的補 NULL。條件寫在 &lt;code>ON&lt;/code> 裡的時候，它參與的是「配不配得上」這個判斷——宗翰沒有訂單所以配不上任何評價，佳穎的第二張訂單配不上五星評價，照定義都補 NULL 留下來。&lt;/p>
&lt;p>條件寫在 &lt;code>WHERE&lt;/code> 裡的時候，關係已經組完了，補上的那些 NULL 也已經在裡面。這時候問「星等等於 5」，那些補 NULL 的列答不出 5，於是被篩掉。&lt;strong>外連接剛剛保護下來的列，&lt;code>WHERE&lt;/code> 這一步又把它們拿掉了。&lt;/strong>&lt;/p>
&lt;p>內連接底下看不出差別，是因為它本來就不保留配不上的列。沒有被保護的列可以被抵銷，所以兩種寫法自然同值。&lt;/p>
&lt;p>開頭那段顧客接訂單、再接評價的查詢有三張表，第二個 &lt;code>LEFT JOIN&lt;/code> 保護的「左邊」因此是顧客與訂單累積出來的結果；鏈越長，保護的範圍怎麼跟著累積走，是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/join-left-operand-accumulates/" data-link-title="SQL.4 鏈式連接的左運算元：累積方式與外連接的保護範圍" data-link-desc="每個 JOIN 的左運算元由哪些表合成、ON 因此引用得到哪些欄位、LEFT 與 RIGHT 各自保護哪些列，以及 WHERE 引用鏈尾的表時哪些列會被撤掉保護">SQL.4 鏈式連接的左運算元&lt;/a> 的題目。&lt;/p>
&lt;h2 id="保護存不存活的測試把補了-null-的那一列代進條件">保護存不存活的測試：把補了 NULL 的那一列代進條件&lt;/h2>
&lt;p>&lt;code>WHERE&lt;/code> 只留下判成真的列。而條件的答案有三種——真、假、以及&lt;strong>未知&lt;/strong>：拿 &lt;code>NULL&lt;/code> 去跟任何值比較，答案既不是真也不是假（三值邏輯的推導，以及它讓 &lt;code>NOT IN&lt;/code> 整段失效的形態，在 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/join-changes-rows-and-nulls/" data-link-title="SQL.6 連接之後的列數與空缺：列數膨脹、外連接補的 NULL 與三值邏輯" data-link-desc="連接讓聚合多算的機制與計數、加總、平均、中位數各自的修法，以及外連接補出來的 NULL 在 count、等號比較、NOT IN 與 NOT EXISTS 上的行為">SQL.6 連接之後的列數與空缺：列數膨脹、外連接補的 NULL 與三值邏輯&lt;/a>）。&lt;/p>
&lt;p>外連接補上的那些列，右表欄位全是 &lt;code>NULL&lt;/code>，所以問題變成一句話：&lt;strong>這個條件對「什麼都沒有」這一列給出什麼答案。&lt;/strong> 真的留下，假與未知都被丟掉。&lt;/p></description><content:encoded><![CDATA[<p><code>ON</code> 說的是 <code>JOIN</code> 左右兩邊的列怎麼配成一個 <a href="/blog/backend/01-database/sql/knowledge-cards/relation/" data-link-title="Relation（關係）" data-link-desc="看到 SQL 教材說「組成一個關係」而不確定它指的是不是資料表時">relation</a>，<code>WHERE</code> 說的是從配好的關係裡篩掉什麼。這兩件事落在求值順序的不同步：<code>FROM</code> 那一步把關係組出來，<code>WHERE</code> 那一步才開始篩。</p>
<p>本篇要反覆用到這兩件事的名字。<strong>配對</strong>指關係還在組的那個過程——哪一列跟哪一列湊成一列；<strong>成品</strong>指關係組完之後得到的那一批列——已經湊好、包含補了 NULL 的那些列在內的整批資料。這兩個詞是本篇為了指認這兩個時刻而取的稱呼，SQL 的標準術語裡沒有它們。</p>
<p>內連接底下，條件寫在 <code>ON</code> 與寫在 <code>WHERE</code> 的結果相同，於是它們看起來可以互換。<a href="/blog/backend/01-database/sql/knowledge-cards/outer-join/" data-link-title="Outer Join（外連接）" data-link-desc="想確認 LEFT / RIGHT / FULL 各自保護哪一側、或連接之後有些人從名單上消失時">外連接</a>底下不同，而不同的地方正好是外連接存在的理由。條件擺在哪一邊不影響查詢跑不跑得動，只影響外連接剛保護下來的那些列留不留得住。保護指 <code>LEFT JOIN</code> 把左表配不到對象的列也留在結果裡、右表那幾欄補上 <code>NULL</code>。<code>LEFT JOIN</code> 退化成內連接的名單，來源就是把條件放進 <code>WHERE</code> 這一步。</p>
<h2 id="同一個條件放-on-與放-where-的結果">同一個條件放 ON 與放 WHERE 的結果</h2>
<p>書店想要一份名單：全部顧客都要列出來，而評價欄只顯示五星的那些。條件寫進 <code>ON</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">姓名</span><span class="p">,</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">星等</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="err">顧客</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="err">訂單</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">顧客編號</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">顧客編號</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="err">評價</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">訂單編號</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">訂單編號</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">星等</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span></span></span></code></pre></div><p>回來四列：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">佳穎  5
</span></span><span class="line"><span class="ln">2</span><span class="cl">佳穎  NULL
</span></span><span class="line"><span class="ln">3</span><span class="cl">宗翰  NULL
</span></span><span class="line"><span class="ln">4</span><span class="cl">雅文  NULL</span></span></code></pre></div><p>三個顧客都在。佳穎出現兩次，因為她有兩張訂單——第一張有五星評價，第二張沒有評價可配，補 NULL。（一位顧客變成兩列是連接在改變列數，本篇只處理條件決定的留不留，連接怎麼改變列數在 <a href="/blog/backend/01-database/sql/join-changes-rows-and-nulls/" data-link-title="SQL.6 連接之後的列數與空缺：列數膨脹、外連接補的 NULL 與三值邏輯" data-link-desc="連接讓聚合多算的機制與計數、加總、平均、中位數各自的修法，以及外連接補出來的 NULL 在 count、等號比較、NOT IN 與 NOT EXISTS 上的行為">SQL.6 連接之後的列數與空缺</a>。）</p>
<p>同一個星等條件改放 <code>WHERE</code>，其餘不動：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">姓名</span><span class="p">,</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">星等</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="err">顧客</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="err">訂單</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">顧客編號</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">顧客編號</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="err">評價</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">訂單編號</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">訂單編號</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">星等</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w"></span><span class="c1">-- 佳穎  5</span></span></span></code></pre></div><p>宗翰、雅文與佳穎的第二張訂單全部消失，結果與內連接相同。書店要的是全部顧客，所以這一版的答案錯了，而查詢照樣跑、不報錯——這一類錯的共同性質在 <a href="/blog/backend/01-database/sql/well-formed-is-not-correct/" data-link-title="SQL.13 查詢的合法性與答案的正確性：引擎檢查的範圍、答案錯掉的成因與查證方法" data-link-desc="合法與問對了分別由誰負責、答案錯掉時引擎為什麼不報錯，以及靜態分析、約束與反例資料各自判得出哪些錯">SQL.13 查詢的合法性與答案的正確性：引擎檢查的範圍、答案錯掉的成因與查證方法</a>。需求若恰好只要有五星評價的那些，答案就對了，剩下的是文字的問題：<code>LEFT</code> 仍然寫在那裡，照字面讀的人會以為沒配到的顧客都在，那是 <a href="/blog/backend/01-database/sql/declared-intent-vs-behaviour/" data-link-title="SQL.20 關鍵字的宣告與引擎的行為：宣告落空的形態與查證" data-link-desc="LEFT JOIN、CROSS JOIN、DISTINCT、COALESCE 與表的別名各自對讀的人宣告了什麼、宣告落空時下一個修改的人付出什麼代價，以及把關鍵字換成最弱的版本再跑一次的查證動作">SQL.20 關鍵字的宣告與引擎的行為：宣告落空的形態與查證</a> 的主題。</p>
<h2 id="條件放-on-與放-where-在外連接底下分岔的原因">條件放 ON 與放 WHERE 在外連接底下分岔的原因</h2>
<p><code>LEFT JOIN</code> 的定義是左邊每一列都留著，右邊配不上的補 NULL。條件寫在 <code>ON</code> 裡的時候，它參與的是「配不配得上」這個判斷——宗翰沒有訂單所以配不上任何評價，佳穎的第二張訂單配不上五星評價，照定義都補 NULL 留下來。</p>
<p>條件寫在 <code>WHERE</code> 裡的時候，關係已經組完了，補上的那些 NULL 也已經在裡面。這時候問「星等等於 5」，那些補 NULL 的列答不出 5，於是被篩掉。<strong>外連接剛剛保護下來的列，<code>WHERE</code> 這一步又把它們拿掉了。</strong></p>
<p>內連接底下看不出差別，是因為它本來就不保留配不上的列。沒有被保護的列可以被抵銷，所以兩種寫法自然同值。</p>
<p>開頭那段顧客接訂單、再接評價的查詢有三張表，第二個 <code>LEFT JOIN</code> 保護的「左邊」因此是顧客與訂單累積出來的結果；鏈越長，保護的範圍怎麼跟著累積走，是 <a href="/blog/backend/01-database/sql/join-left-operand-accumulates/" data-link-title="SQL.4 鏈式連接的左運算元：累積方式與外連接的保護範圍" data-link-desc="每個 JOIN 的左運算元由哪些表合成、ON 因此引用得到哪些欄位、LEFT 與 RIGHT 各自保護哪些列，以及 WHERE 引用鏈尾的表時哪些列會被撤掉保護">SQL.4 鏈式連接的左運算元</a> 的題目。</p>
<h2 id="保護存不存活的測試把補了-null-的那一列代進條件">保護存不存活的測試：把補了 NULL 的那一列代進條件</h2>
<p><code>WHERE</code> 只留下判成真的列。而條件的答案有三種——真、假、以及<strong>未知</strong>：拿 <code>NULL</code> 去跟任何值比較，答案既不是真也不是假（三值邏輯的推導，以及它讓 <code>NOT IN</code> 整段失效的形態，在 <a href="/blog/backend/01-database/sql/join-changes-rows-and-nulls/" data-link-title="SQL.6 連接之後的列數與空缺：列數膨脹、外連接補的 NULL 與三值邏輯" data-link-desc="連接讓聚合多算的機制與計數、加總、平均、中位數各自的修法，以及外連接補出來的 NULL 在 count、等號比較、NOT IN 與 NOT EXISTS 上的行為">SQL.6 連接之後的列數與空缺：列數膨脹、外連接補的 NULL 與三值邏輯</a>）。</p>
<p>外連接補上的那些列，右表欄位全是 <code>NULL</code>，所以問題變成一句話：<strong>這個條件對「什麼都沒有」這一列給出什麼答案。</strong> 真的留下，假與未知都被丟掉。</p>
<table>
  <thead>
      <tr>
          <th>條件</th>
          <th>對補 NULL 的那一列</th>
          <th>保護</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>評價.星等 = 5</code></td>
          <td>NULL 比不出來，判成未知</td>
          <td>取消</td>
      </tr>
      <tr>
          <td><code>評價.星等 IS NOT NULL</code></td>
          <td>判成假</td>
          <td>取消</td>
      </tr>
      <tr>
          <td><code>評價.星等 IS NULL</code></td>
          <td>判成真</td>
          <td>保住</td>
      </tr>
      <tr>
          <td><code>評價.星等 IS NULL OR 評價.星等 = 5</code></td>
          <td>前半判成真，整串就是真</td>
          <td>保住</td>
      </tr>
      <tr>
          <td><code>COALESCE(評價.星等, 0) &gt;= 0</code></td>
          <td>NULL 先被換成 0，判成真</td>
          <td>保住</td>
      </tr>
  </tbody>
</table>
<p><strong>能不能保住不看用了哪個運算子。</strong> <code>IS NOT NULL</code> 看起來與 <code>IS NULL</code> 成對，而在表上的「保護」欄一個是取消、一個是保住，正是因為它對空缺判成假；而 <code>OR</code> 與 <code>COALESCE</code> 這兩種寫法把未知轉成真，保護就留住了。</p>
<p><strong>這張表上的條件全部提到右表，而左表的條件進不了這張表。</strong> 補 <code>NULL</code> 的那一列上左表欄位仍然是原本的值，拿它去問什麼都判得出真假，所以保護一定保得住——這個測試對左表條件恆為真，什麼都判別不出來。左表條件實際的問題出在寫進 <code>ON</code> 的時候：它篩不掉左表的任何一列，還會讓本來配得上的那些配對判假。同一個條件 <code>顧客.姓名 &lt;&gt; '佳穎'</code> 放在三個位置：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- 放 WHERE：佳穎被篩掉，宗翰與雅文的保護完好
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">姓名</span><span class="p">,</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">訂單編號</span><span class="p">,</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">星等</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="err">顧客</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="err">訂單</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">顧客編號</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">顧客編號</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w"></span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="err">評價</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">訂單編號</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">訂單編號</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">星等</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">姓名</span><span class="w"> </span><span class="o">&lt;&gt;</span><span class="w"> </span><span class="s1">&#39;佳穎&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w"></span><span class="c1">-- 宗翰  NULL  NULL
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">-- 雅文  NULL  NULL
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="c1">-- 放進顧客接訂單的 ON：佳穎與她的兩張訂單判假、配不上，照 LEFT 的定義補 NULL 留下
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">姓名</span><span class="p">,</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">訂單編號</span><span class="p">,</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">星等</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="err">顧客</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w"></span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="err">訂單</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">顧客編號</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">顧客編號</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">姓名</span><span class="w"> </span><span class="o">&lt;&gt;</span><span class="w"> </span><span class="s1">&#39;佳穎&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w"></span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="err">評價</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">訂單編號</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">訂單編號</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">星等</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w"></span><span class="c1">-- 佳穎  NULL  NULL     三位顧客全回來，佳穎的兩張訂單一起消失
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1">-- 宗翰  NULL  NULL
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1">-- 雅文  NULL  NULL
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w"></span><span class="c1">-- 放進接評價的 ON：顧客與訂單在上一步已經配好，佳穎的兩張訂單留著，只是配不到評價
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">姓名</span><span class="p">,</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">訂單編號</span><span class="p">,</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">星等</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="err">顧客</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w"></span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="err">訂單</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">顧客編號</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">顧客編號</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w"></span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="err">評價</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">訂單編號</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">訂單編號</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="err">評價</span><span class="p">.</span><span class="err">星等</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">姓名</span><span class="w"> </span><span class="o">&lt;&gt;</span><span class="w"> </span><span class="s1">&#39;佳穎&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w"></span><span class="c1">-- 佳穎  101   NULL
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1">-- 佳穎  102   NULL
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1">-- 宗翰  NULL  NULL
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1">-- 雅文  NULL  NULL</span></span></span></code></pre></div><p>所以左表條件根本不需要走這個測試，它一開始就屬於 <code>WHERE</code>。</p>
<p><strong>這個測試回答的是保護存不存活，不回答條件該放哪一邊。</strong> 歸屬要另外問一句：這個條件描述的是配對，還是成品。「只跟五星的評價配」描述的是配對，屬於 <code>ON</code>；「最後的名單裡只要五星那幾列」描述的是成品，屬於 <code>WHERE</code>。兩句話說的是不同的事，而內連接讓這兩種寫法碰巧回同一批列。</p>
<p>多數形態上「保護存不存活」與「條件該放哪一邊」這兩個問題同向——取消了保護的條件，通常正是因為它在描述成品。<code>IS NULL</code> 是這兩個問題答案不同向的那一個形態：它保住了保護，而它要的正好是那些被保護下來的列，所以它同時是「保護存活」與「屬於 <code>WHERE</code>」。下一節寫的就是這個形態。</p>
<h2 id="找沒配到的那些列is-null-條件的歸屬">找沒配到的那些列：IS NULL 條件的歸屬</h2>
<p>有時候要的正是「沒配到的那些」。書店想知道哪些顧客一張單都沒下過：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">姓名</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="err">顧客</span><span class="w"> </span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="err">訂單</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">顧客編號</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">顧客</span><span class="p">.</span><span class="err">顧客編號</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="err">訂單</span><span class="p">.</span><span class="err">訂單編號</span><span class="w"> </span><span class="k">IS</span><span class="w"> </span><span class="k">NULL</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="c1">-- 宗翰、雅文</span></span></span></code></pre></div><p>這個條件描述的是成品，所以它屬於 <code>WHERE</code>。它與上面那個「退化成內連接」的形態看起來很像——同樣是在 <code>WHERE</code> 裡引用右表欄位——而要的結果剛好相反：這一個挑的正是外連接補上 NULL 的那些列。</p>
<p><strong>分辨這兩種形態的仍然是那個問題：這個條件描述的是配對，還是成品。</strong> 「一張單都沒下過」是關於成品的敘述，所以它在 <code>WHERE</code> 是對的位置。</p>
<p>同一題「找沒消費過的顧客」換一個切面，走得到同一個結論。<a href="/blog/backend/01-database/sql/grouping-key-decides-the-unit/" data-link-title="SQL.9 分組鍵的選擇：鍵與一組所代表的對象" data-link-desc="外連接之後拿右表欄位當鍵的後果、輸出非分組欄時 MySQL、DuckDB 與 SQLite 的反應、補進鍵的欄位要滿足的函數相依，以及改用 NOT EXISTS 回答的那一類問題">SQL.9 分組鍵的選擇：鍵與一組所代表的對象</a> 從「沒消費的人不會出現在訂單表」這個業務事實走到 <code>NOT EXISTS</code>，並說明在外連接之後分組、再數出零的那種寫法，那個零是查詢自己製造再讀回去的產物。</p>
]]></content:encoded></item></channel></rss>