SQLを扱っていると、意図したデータが正しく取得できない、あるいは検索条件が正しく機能していないと感じる場面に遭遇することがあります。
その原因の多くは、SQL特有の概念である「3値論理」と、それが引き起こす「NULL」の振る舞いにあります。
多くのプログラミング言語では、条件式の結果は「真(TRUE)」か「偽(FALSE)」のどちらかになる2値論理が採用されています。
しかし、SQLの世界ではこれらに加えて「未知(UNKNOWN)」という第3の状態が存在します。
この第3の値を正しく理解していないと、アプリケーションに重大なバグを混入させるリスクがあります。
本記事では、SQLの3値論理の仕組みから、実務で陥りやすいNULLの罠、そして正しい判定条件の書き方について詳しく解説します。
SQLにおける3値論理の基本
SQLにおける論理演算は、「TRUE」「FALSE」「UNKNOWN」の3つの値で構成されています。
これは、リレーショナルデータベースが「欠落している情報」や「不明な情報」を表現するためにNULLを使用しているためです。
NULLが含まれる比較演算や論理演算を行うと、その結果はTRUEやFALSEではなくUNKNOWNになります。
なぜ「未知(UNKNOWN)」が必要なのか
データベースには、必ずしもすべてのデータが揃っているわけではありません。
例えば、アンケートの回答で年齢が空欄だった場合、その人の年齢が「20歳以上である」か「20歳未満である」かを判定することは不可能です。
この「判定できない状態」を論理的に扱うために、SQLではUNKNOWNという値が必要になります。
NULLは値そのものではなく、「値が存在しない状態」を示す目印であると理解することが重要です。
3値論理の演算表(真理値表)
3値論理を理解するためには、AND、OR、NOTの演算がどのように変化するかを把握しておく必要があります。
特にUNKNOWNが絡む演算は、直感に反する場合があるため注意が必要です。
AND演算のルール
AND演算では、片方がFALSEであれば結果は必ずFALSEになります。
しかし、片方がTRUEであっても、もう片方がUNKNOWNであれば結果はUNKNOWNになります。
| 演算内容 | 結果 |
|---|---|
| TRUE AND UNKNOWN | UNKNOWN |
| FALSE AND UNKNOWN | FALSE |
| UNKNOWN AND UNKNOWN | UNKNOWN |
OR演算のルール
OR演算では、片方がTRUEであれば結果は必ずTRUEになります。
片方がFALSEで、もう片方がUNKNOWNの場合、結果はUNKNOWNになります。
| 演算内容 | 結果 |
|---|---|
| TRUE OR UNKNOWN | TRUE |
| FALSE OR UNKNOWN | UNKNOWN |
| UNKNOWN OR UNKNOWN | UNKNOWN |
NOT演算のルール
NOT演算は値を反転させますが、UNKNOWNを反転させてもUNKNOWNのままです。
| 演算内容 | 結果 |
|---|---|
| NOT TRUE | FALSE |
| NOT FALSE | TRUE |
| NOT UNKNOWN | UNKNOWN |
NULLが引き起こす比較演算の罠
SQLで最も間違いやすいのが、NULLと比較演算子を用いた判定です。
プログラミングの感覚で col = NULL と記述しても、期待した結果は得られません。
比較演算の結果は常にUNKNOWNになる
SQLにおいて、NULLに対する比較演算(=, <>, <, >など)の結果は、すべてUNKNOWNになります。
たとえ NULL = NULL という比較であっても、結果はTRUEではなくUNKNOWNです。
以下のクエリを見てみましょう。
-- 年齢がNULLのユーザーを抽出したい場合の誤った例
SELECT * FROM users WHERE age = NULL;
このクエリは、たとえageカラムにNULLが入っているレコードがあっても、1件もヒットしません。
なぜなら、WHERE句は条件式の結果が「TRUE」となる行のみを抽出するという性質を持っているからです。
age = NULL の結果はUNKNOWNであるため、WHERE句のフィルタリング条件を満たしません。
IS NULLによる正しい判定
NULLかどうかを判定するには、専用の述語である IS NULL または IS NOT NULL を使用する必要があります。
-- 正しい書き方
SELECT * FROM users WHERE age IS NULL;
この書き方であれば、期待通りNULLを持つレコードを抽出できます。
実務で最も危険な「NOT IN」とNULLの組み合わせ
3値論理の罠が牙を剥く典型的な例が、サブクエリを利用した NOT IN 句です。
リストの中に一つでもNULLが含まれている場合、NOT IN は常に空の結果を返してしまいます。
なぜNOT INが失敗するのか
例えば、age NOT IN (20, 30, NULL) という条件を考えます。
これは論理的に分解すると以下のようになります。
NOT (age = 20 OR age = 30 OR age = NULL)
もし age = 25 だった場合、式は NOT (FALSE OR FALSE OR UNKNOWN) となります。
OR演算のルールに従うと、括弧内は UNKNOWN に集約されます。
そして NOT UNKNOWN は UNKNOWN です。
結果として、どの行に対してもTRUEが返らないため、全体の検索結果が0件になってしまいます。
これを防ぐためには、サブクエリ内で WHERE col IS NOT NULL と明示的にNULLを除外するか、NOT EXISTS を使用する必要があります。
WHERE句以外での3値論理の振る舞い
3値論理の影響を受けるのは、WHERE句のフィルタリングだけではありません。
CASE式におけるNULL
CASE式の WHEN 句もWHERE句と同様に、評価結果がTRUEの場合のみ処理が実行されます。
SELECT
name,
CASE
WHEN age = NULL THEN '不明' -- ここは決して実行されない
ELSE '登録済み'
END AS status
FROM users;
上記のコードでは、age = NULL は常にUNKNOWNとなるため、「不明」というラベルが振られることはありません。
CASE式でも必ず IS NULL を使用しなければなりません。
集計関数とNULL
多くの集計関数(COUNT, SUM, AVGなど)は、計算の対象からNULLを無視します。
ただし、COUNT(*) だけは例外的にNULLを含む行数そのものをカウントします。
平均値を求める AVG(age) では、NULLの行は分母にも分子にも含まれないため、実データのみの平均が算出されます。
これが意図した動作であれば問題ありませんが、未入力値を0として扱いたい場合は COALESCE 関数などで補完する必要があります。
NULLの罠を回避するためのベストプラクティス
3値論理によるバグを防ぐためには、日頃から以下の習慣を身につけておくことが大切です。
1. NOT NULL制約を積極的に活用する
最も根本的な対策は、可能な限りカラムにNOT NULL制約を付与することです。
NULLが存在しなければ、論理演算は従来の2値論理として振る舞い、予測可能性が格段に向上します。
デフォルト値を設定できるのであれば、空文字や0などで代用できないか検討しましょう。
2. 比較には必ず述語を使用する
NULLの可能性があるカラムを扱う際は、常に IS NULL や IS NOT NULL を使う意識を持ちましょう。
等号演算子(=)を使ってしまう癖を捨てることが、トラブルを避ける第一歩です。
3. COALESCE関数でデフォルト値を設定する
COALESCE 関数を使用すると、NULLを別の値に置換して評価することができます。
-- 年齢がNULLの場合は0として比較する
SELECT * FROM users WHERE COALESCE(age, 0) < 20;
このように記述することで、比較結果がUNKNOWNになるのを防ぎ、確実な2値論理の範囲で制御できます。
4. NOT INよりもNOT EXISTSを選択する
前述した通り、NOT IN はNULLの影響を強く受けます。
相関サブクエリを用いた NOT EXISTS は、結果がTRUEかFALSEのいずれかになるように設計されているため、NULLによる全滅リスクを回避できます。
パフォーマンスの面でも NOT EXISTS が有利に働くことが多いため、こちらを優先的に使うのが推奨されます。
まとめ
SQLの3値論理は、データベースを扱うエンジニアにとって避けては通れない非常に重要な概念です。
「TRUE」「FALSE」に加えて「UNKNOWN」が存在することを常に意識し、NULLが演算に混入した際の挙動を把握しておく必要があります。
特に比較演算において = ではなく IS NULL を使うことや、NOT IN の危険性を知っておくだけで、多くの論理的なバグを未然に防ぐことができます。
データベースの設計段階からNULLを許容するかどうかを慎重に判断し、適切な制約とクエリ記述を心がけましょう。
正しい知識を持って3値論理と向き合うことが、堅牢で信頼性の高いデータベースアプリケーションの構築に繋がります。
