SQLのNULLを理解する:欠損値との向き合い方
SQLでデータ分析をしていると、必ずと言っていいほど「NULL」という値に遭遇します。このNULL、一体何者なのでしょうか?単なる空っぽ、それとも特別な意味を持つ値?
最初は「0」や空文字と同じようなものかと思ってしまうかもしれません。しかし、SQLの世界ではNULLは非常に独特な存在です。その扱い方を間違えると、予期せぬ結果を招き、データ分析の精度を大きく下げてしまうこともあります。
この記事では、SQLにおけるNULLの基本的な性質から、具体的なSQL文での挙動、そしてNULLを安全かつ効果的に扱うためのテクニックまでを、分かりやすく解説していきます。
NULLとは何か?:未知の値の表現
まず、NULLの基本的な定義から確認しましょう。
NULLは、値が存在しない、あるいは不明であることを示す特別なマーカーです。
これは、数値の「0」や空文字('')とは全く異なります。
- 0: 数値としてのゼロという値が存在します。
- 空文字 (
''): 文字列としての空の値が存在します。 - NULL: 値そのものが存在しない、あるいは未知であることを示します。
例えば、顧客テーブルに「年齢」という列があるとします。
ある顧客の年齢が不明な場合、それは「0歳」でも「不明な文字列」でもなく、「NULL」として記録されます。
この「値が存在しない」という性質が、SQLでのNULLの振る舞いをユニークなものにしています。
NULLとの比較:なぜ期待通りに動かないのか?
NULLの最も特徴的な性質は、比較演算子 (=, !=, <, > など) との組み合わせで、常に「不明」という結果を返すことです。
これは、NULLが「値が存在しない」ことを示すため、他の値と比較しようとしても、その比較対象となる値自体が存在しないからです。
例えば、以下のようなテーブルがあるとします。
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
age INTEGER
);
INSERT INTO users (name, age) VALUES
('Alice', 30),
('Bob', NULL),
('Charlie', 25);
このテーブルで、「年齢が30歳の人」を検索しようとした場合、WHERE age = 30 と記述します。
SELECT name FROM users WHERE age = 30;
このクエリは、Aliceのみを返します。
では、「年齢がNULLの人」を検索しようとしたらどうなるでしょうか?
WHERE age = NULL と書きたくなりますが、これは期待通りに動きません。
SELECT name FROM users WHERE age = NULL;
このクエリは、何も返しません。
なぜなら、NULL = NULL という比較は、真偽値(TRUE/FALSE)ではなく、「不明」(UNKNOWN)という結果になるからです。
WHERE句は、条件がTRUEになった行のみを抽出するため、UNKNOWNは結果として扱われません。
同様に、WHERE age != NULL も、NULL以外の値と比較しようとしても、その比較対象が不明なため、常にUNKNOWNとなり、期待通りの結果は得られません。
NULLを安全に扱うための IS NULL と IS NOT NULL
NULLとの比較には、専用の演算子 IS NULL と IS NOT NULL を使用します。
これらは、値が存在するかどうかを直接判定するため、期待通りの結果を得られます。
「年齢がNULLの人」を検索するには、以下のように記述します。
SELECT name FROM users WHERE age IS NULL;
これにより、Bobが正しく取得できます。
逆に、「年齢がNULLでない人」を検索するには、IS NOT NULL を使用します。
SELECT name FROM users WHERE age IS NOT NULL;
これにより、AliceとCharlieが取得できます。
この IS NULL と IS NOT NULL は、NULLを扱う上での基本中の基本です。必ず覚えておきましょう。
NULLと集計関数:期待される挙動と注意点
SQLの集計関数(COUNT, SUM, AVG, MAX, MIN など)は、NULL値をどのように扱うのでしょうか?
多くの場合、集計関数はNULL値を無視して計算を行います。
これは、NULLが「値が存在しない」ことを示すため、計算の対象から除外するのが自然だからです。
例えば、先ほどのusersテーブルで、COUNT(*) と COUNT(age) の違いを見てみましょう。
SELECT
COUNT(*) AS total_rows,
COUNT(age) AS non_null_ages
FROM users;
このクエリの実行結果は以下のようになります。
| total_rows | non_null_ages |
|---|---|
| 3 | 2 |
COUNT(*)は、テーブルの全ての行数をカウントします。そのため、結果は3です。COUNT(age)は、age列がNULLでない行のみをカウントします。Bobの年齢はNULLなので、AliceとCharlieの2行のみがカウントされ、結果は2となります。
SUM や AVG も同様に、NULL値を無視して計算します。
SELECT
SUM(age) AS total_age,
AVG(age) AS average_age
FROM users;
このクエリの実行結果は以下のようになります。
| total_age | average_age |
|---|---|
| 55 | 27.5 |
SUM(age)は、30 + 25 = 55 となります。AVG(age)は、(30 + 25) / 2 = 27.5 となります。
BobのNULL値は計算に含まれていません。
注意:AVG 関数と COUNT(column) の関係
AVG(column) は、内部的には SUM(column) / COUNT(column) で計算されることが多いです。そのため、COUNT(column) がNULLを無視するのと同様に、AVG(column) もNULLを無視します。
もし、NULLを0として扱って平均を計算したい場合は、COALESCE 関数などを用いて明示的に変換する必要があります。
MAX と MIN もNULL値を無視します。ただし、NULL値が存在する場合、それらの関数はNULL以外の値の中から最大値・最小値を返します。
NULL値がテーブルに存在する場合、集計結果が期待と異なることはよくあります。集計関数がNULLをどのように扱うかを理解しておくことが重要です。
NULLを安全に扱うためのテクニック
NULLの扱いに慣れてくると、その特性を理解し、より安全にSQLを記述できるようになります。
ここでは、いくつかの実践的なテクニックを紹介します。
1. COALESCE 関数:NULLを代替値に置き換える
COALESCE 関数は、引数リストの中から最初に見つかったNULLでない値を返します。
これにより、NULL値を特定のデフォルト値に置き換えることができます。
例えば、先ほどのusersテーブルで、年齢がNULLの場合は「不明」という文字列に置き換えて表示したい場合、以下のように記述します。
SELECT name, COALESCE(age, -1) AS age_display
FROM users;
このクエリの実行結果は以下のようになります。
| name | age_display |
|---|---|
| Alice | 30 |
| Bob | -1 |
| Charlie | 25 |
ここでは、NULLだったBobの年齢を-1に置き換えています。
集計関数と組み合わせて使うこともよくあります。例えば、NULLの売上を0として合計したい場合などです。
SELECT SUM(COALESCE(sales, 0)) AS total_sales
FROM orders;
COALESCEは、NULLを扱いたい場面で非常に強力な関数です。
2. NULLIF 関数:特定の値をNULLに置き換える
NULLIF 関数は、2つの引数を比較し、もし2つが等しければNULLを返します。そうでなければ、最初の引数を返します。
これは、特定の値をNULLとして扱いたい場合に便利です。
例えば、注文数量が0の場合、それをNULLとして扱いたい場合などに使えます。
SELECT
order_id,
NULLIF(quantity, 0) AS effective_quantity
FROM order_details;
もしquantityが0であれば、effective_quantityはNULLになります。そうでなければ、元のquantityの値が返されます。
3. CASE WHEN 文でのNULL処理
CASE WHEN 文は、条件分岐を行うためのSQL構文ですが、NULLの判定にも柔軟に対応できます。
IS NULL や IS NOT NULL と組み合わせて、NULLの場合に特定の処理を行うことができます。
SELECT
name,
CASE
WHEN age IS NULL THEN '年齢不明'
WHEN age < 20 THEN '若年層'
ELSE '成人'
END AS age_group
FROM users;
このクエリは、年齢がNULLの場合は「年齢不明」、20歳未満の場合は「若年層」、それ以外の場合は「成人」と表示します。
4. テーブル設計におけるNULLの検討
そもそも、データベース設計の段階で、どの列にNULLを許容するかを慎重に検討することが重要です。
必須の項目であればNULLを許容しない(NOT NULL制約を付ける)などの対策が考えられます。
しかし、必ずしも全てのデータが常に存在するとは限りません。例えば、オプションの項目や、まだ情報が入力されていない項目などでは、NULLを許容することが適切です。
NULLを許容する際は、その値がどのような意味を持つのかを明確にしておくことが、後々のデータ分析やアプリケーション開発で混乱を防ぐ鍵となります。
まとめ:NULLは「未知」を正しく扱うための鍵
SQLにおけるNULLは、単なる空の値ではなく、「値が存在しない」「未知である」ことを示す特別な概念です。
その比較演算子との独特な挙動や、集計関数での無視される性質を理解することは、正確なデータ分析を行う上で不可欠です。
- NULLは0や空文字とは異なる。
- NULLとの比較には
IS NULLやIS NOT NULLを使う。 - 集計関数は基本的にNULLを無視する。
COALESCEやNULLIF、CASE WHENを活用してNULLを安全に処理する。
これらの知識を身につけることで、SQLでのデータ操作における多くの「なぜ?」が解消され、より自信を持ってデータと向き合えるようになるはずです。
NULLの扱いに戸惑うことはありません。それは、データが持つ「不確実性」や「未知」を正しく表現するためのSQLの機能なのです。