副問い合わせにおいて、副問い合わせ部分で抽出された値にNULL値が含まれていて、その結果をNOT INの条件として指定した場合、結果が0件になってしまうので注意が必要です。
対処方法としては、副問い合わせの結果からNULLを除外できればよいです。例えば、副問い合わせの中でwhere句にIS NOT NULLを追加したり、IFNULL()関数などでNULLを値に変換したりすることで、NULLは排除できます。そうすることで、前述の問題は回避できます。
ですが、それを何の脈絡もない文法として覚えるのでは忘れやすくなってしまいますので、その理由を探って行きましょう。
以下のようなEMPLOYEEテーブルに対して、SQLを実行します。
・・・ |
NAME |
SALARY |
・・・ |
|
ALLEN |
1200 |
|
|
BRIAN |
1400 |
|
|
CHRIS |
NULL |
|
|
DAVID |
1600 |
|
SELECT * FROM EMPLOYEE WHERE SALARY IN (1200, 1400, NULL);
結果
・・・ |
NAME |
SALARY |
・・・ |
|
ALLEN |
1200 |
|
|
BRIAN |
1400 |
|
2件
SELECT * FROM EMPLOYEE WHERE SALARY NOT IN (1200, 1400, NULL);
結果
0件
説明のために、(1200, 1400, NULL)となっていますが、実際にはここが副問い合わせになっている想定です。副問い合わせの部分だけを実行した結果が、1200, 1400, NULLの3つの値だったということです。(直接、値を設定する場合はNULLを入力しなければよいだけのことです。)
NULLが含まれている結果に対してINを利用した①の方は、もちろん1200の値を持つALLENさんと、1400の値を持つBRIANさんのレコードは抽出条件に合うため表示されますが、CHRISさんのレコードが表示されませんでした。NOT INを利用した②の方は結果が0件となってしまいました。1600の値を持つDAVIDさんのレコードが表示されると予想した方がいると思いますが、そうはなりませんでした。
では、なぜ①でCHRISさんのレコードが表示されず、②で0件になってしまったのでしょうか。その理由は、WHERE 句に注目するとわかります。
まず、INを利用した①では、抽出条件が【SALARY IN (1200, 1400, NULL)】となっていますが、INを使わずに表現すると、【SALARY = 1200 OR SALARY = 1400 OR SALARY = NULL】となります。
ここで、「SALARY = NULL」の結果はFALSEです。NULLは値を持たないため、比較演算の結果はNULLとなり、FALSEのときと同じ挙動になります。よって、SALARY = 1200がtrueとなるレコードと、SALARY = 1400 がtrueとなるレコードのみが抽出対象になります。CHRISさんが抽出されるようにする場合の抽出条件は、【SALARY IS NULL】です。
次に、NOT INを利用した②の抽出条件である【SALARY NOT IN (1200, 1400, NULL)】も同様に、INを使わずに表現すると、【SALARY != 1200 AND SALARY != 1400 AND SALARY != NULL】となります。
条件全体でみると、3つの条件がANDでつなげられているので、3つの条件がすべてtrueとなるレコードが抽出対象になります。そして、ここでも「SALARY != NULL」の結果はNULLのためFALSEと判定されます。
ということは、他の条件がいくらtrueであっても、NULLとの比較が含まれる部分がFALSEとなるので、全体的にはFALSEとなってしまいます。条件式の結果が常にFALSEなので、どのレコードも対象にはなりません。よって、エラーではなく、抽出対象が0件という結果になります。
以上のように、INの条件を紐解いて行くと、副問い合わせとNOT INを組み合わせると結果が0件となる可能性があることの仕組みが見えてきます。