フィア・オブ・ジ・アンノウン
NULLを一般値として扱う。または一般値をNULLとして扱う。
欠けている値を区別する
データベースにはデータに値がない状況は必然的に発生する。
列の一部に値がない状態で行を挿入しないといけないような場合もある。
SQLは以下のような値を持たない列において、その値を予約語NULLで表現する。
- null(無)
- unknown(不明)
- inapplicable(適用不能)
SQLのテーブルとクエリでNULLを効果的に使用する方法はたくさんある。
- 行作成時点では不明な値に対してNULLを使用する
- ある行において適用可能な値がない場合にNULLを使用する(例:電気自動車のガソリンの燃料効率など)
- 無効な値が入力された場合に関数はNULLを返せる(例:DAY('2020-13-32'))
- 外部結合は一致しないテーブル列のためにNULLをプレースホルダーとして使用
式でNULLを扱う
以下のSQL文はバグを管理するテーブルである。
hours列にはそのバグの修正時間の見積もりが入っているものとする。
では、以下のクエリを発行した際、hours列の値がNULLだった場合はどうなるか。
SELECT hours + 10 FROM Bugs
hoursには値がないため結果は10が返ってくる??
いえ、結果はNULLが返ってくる。
SQLの標準では文字列とNULLを連結するとNULLが返却される。
NULLは数値のゼロと同じではない。
また、長さがゼロの文字列でもない。
NULLはFALSEと同じものではないためAND、OR、NOTを用いた論理式でも注意が必要。
※OracleとSybaseで振る舞いが異なる。
NULLを許容する列の検索
以下のクエリではBugsテーブルからbug_idが100の行を返しする。
bug_idが100以外の行、またはNULLの行は返却されない。
SELECT * FROM Bugs WHERE bug_id = 100;
では以下のクエリではどうでしょうか。
こちらはbug_idが100以外の行を返却する。
SELECT * FROM Bugs WHERE NOT bug_id = 100;
しかし、上記のクエリでもNULLが割り当てられた行を返却しない。
WHERE句の条件は式がTRUEの場合のみ該当の行を返却する。
NULLを用いた比較ではTRUE、もしくはFALSEではなく不明(unknown)を返却する。
なので以下のようにNULL、または非NULLを検索しても行を取得することはできない。
SELECT * FROM Bugs WHERE bug_detail = NULL;
SELECT * FROM Bugs WHERE NOT bug_detail = NULL;
プリペアードステートメントでNULLを使用する
プリペアードステートメントでパラメータ化したSQLでNULLを使用することは困難。
以下のクエリにてパラメータにパラメータに整数値を使用した場合該当の結果を取得する。
しかし、パラメータにNULLを使用することができない。
SELECT * FROM Bugs WHERE assigned_to = ?;
NULLの使用を避ける
NULLの扱いでクエリが複雑化することを嫌いNULLを使用しないように設計するパターンもある。
NULLの代わりに不明(unknown)や適用不能(inapplicable)を意味する値を新たに定義して格納する。
列に対しNOT NULL制約を宣言してNULLが格納されないようにし、不明(unknown)な値に対して「−1」を格納するなど。
不明(unknown)や適用不能(inapplicable)を意味する値を格納することでNULLを表現する。
しかし、格納する値によってはSUMやAVGのような計算で期待する結果が得られない場合がある。
必要に応じてWHERE句やCASE式を用いて上記の値を排除して取得する必要がある。
アンチパターンの見つけ方
データにNULLが含まれる可能性がある場合は何かしらの問題が発生すると想定をするべき。
アプリケーションのテスト時にテストデータの設計でNULLを含むエッジケースを想定してテストを実施すべき。
アンチパターンを用いても良い場合
NULLを用いること自体がアンチパターンというわけではない。
NULLを一般値として扱ったり一般値をNULLとして扱うことが問題となる。