/note/tech

NULL撲滅委員会

要約:

■ 1. NULL撲滅委員会の序文

  • 目的: NULL撲滅基本宣言への参加を募る
  • NULLの質の悪さ: 最初は感覚に心地よく合致するため自然にシステム設計に忍び込み、気づいたときにはシステムを複雑、非効率的、直観に反する動作をするものにしてしまう
  • 防御方法: NULLの正体をよく知り、どのようなメカニズムによってシステムに猛威を振るうのかを理解すること
  • 本文の目的: 設計の際の心構えとして、NULLから身を守る具体的な方法を明らかにする

■ 2. NULLが悪い5つの理由

  • 3値論理の問題: SQLの作成にあたり、人間の直観に反する3値論理を考慮しなければならない(最大の理由)
  • パフォーマンスの悪化: IS NULL、IS NOT NULLを指定する場合、インデックスが参照されないためパフォーマンスが悪い
  • NULLの伝播: 四則演算またはSQL関数の引数にNULLが含まれると「NULLの伝播」が起こる
  • ホスト言語の非標準化: SQLの結果を受け取るホスト言語において、NULLの組み込み方が標準化されていない
  • 記憶領域の圧迫: NULLは行のどこかに余分なビットを持つことで実装されているため、記憶領域を圧迫したり検索パフォーマンスを悪化させる

■ 3. NULLの伝播の具体例

  • 四則演算:
    • 1 + NULL = NULL
    • 2 - NULL = NULL
    • 3 * NULL = NULL
    • 4 / NULL = NULL
  • 0除算の例外: NULL / 0 = NULLとなり、0除算の場合ですらエラーにならない
  • SQL関数: 多くのSQL関数もNULLに対してはNULLを返す仕様
  • propagateの意味: 「(雑草が)はびこる」のように負のニュアンスを持つ単語で、NULLの厄介者ぶりを表すのにぴったり

■ 4. 意外と知られていない問題

  • ホスト言語のサポート不足: ホスト言語が関係モデルにおけるNULLをサポートしていない現状では大きな問題
  • DBMS間の非互換性: Oracleでは空文字とNULLは区別されないが他のDBMSでは区別されるという現状がまかり通っている
  • 移植性の低下: DBMS間のコードの移植性を大きく下げる要因となる
  • 実装依存の問題: ホスト言語やDBMSの実装次第というところもあり、今後解消される可能性はある

■ 5. NULL完全排除の困難性

  • 永久追放の難しさ: リレーショナル・データベースの世界からNULLを永久追放するのは難しい
  • 現場の感覚: さして重要でない列にNULLが入っているぐらいは眼をつむるのが現場のSEとしての感覚
  • 深い根: NULLがあまりにもリレーショナル・データベースの奥底に根を張る存在だから
  • NOT NULL制約の限界: 全列にNOT NULL制約を付加しても、外部結合やCUBE、ROLLUP付きのGROUP BY句でNULLは簡単に入り込んでくる

■ 6. NULLの便利さと危険性

  • 便利な概念: うまく使えばNULLが大変便利な概念であることは間違いない
  • 扱いの難しさ: 「うまく使う」ことがNULLに関してはとても大変
  • 油断の危険性: うまく御していると思って油断していると背後から一突きされる恐怖
  • 議論の絶えないテーマ: NULLの扱い方は識者の間でも議論が絶えない

■ 7. 識者の見解

  • コッド: NULLが関係モデルにとって不可欠な要素であると確信
  • デイト: NULL撲滅運動の最右翼
  • セルコ: 場末のエンジニアの現実感覚に最も合致する穏健的な人生処方
  • 公式方針: NULLは薬のようなもの。ほどよい使用は有益だが乱用は害を与える。使う必要があるときに適正に使用し、後はなるべく使わないのが最もよい考え方

■ 8. コードの場合の対処法

  • 重要性: 企業コード、顧客コード、県コード、性別コードなど、システムにとって重要な列であることが多い
  • NULL排除の第一標的: 検索や結合のキーとなることも多いため、NULL排除の第一標的となる
  • 解決策: 未コード化用コードを割り振る
  • ISO性別コードの例: 1(男性)、2(女性)の他に、0(未知)、9(適用不能)という二つの未コード化用コードが体系に組み込まれている
  • コッドの分類との対応: 未知と適用不能に対応するコードが採用されている素晴らしい解決

■ 9. コード設計の注意点

  • 文字型での宣言: コード列は必ず文字型で宣言すべき
  • 数値型の問題:
    • 「99999」のようなコードは、後で実際にその顧客が現れる可能性がある
    • 前ゼロが削られてしまう(例: 「008」が「8」になる)
    • ソートがうまく並ばない
    • データの入ったテーブルに対して後から型を変えるのは大変
  • 推奨コード: 「XXXXX」のようなありえない文字列を使用

■ 10. 名前の場合の対処法

  • 方針: コードの場合と同じで、不明を表す値を与える
  • 具体例: 「不明」「UNKNOWN」など、開発チーム内で共通了解の得られた適当な名前
  • 重要度: 名前はコードに比べてキーに使われる頻度が少なく、付加的な意味しか持たない場合が多いため、あまり撲滅に目くじらを立てる必要はない
  • 注意点: 名前をキーに使用しているテーブルがある場合、設計に何か間違いがあると疑うべき

■ 11. 数値の場合の対処法

  • 最良の方法: 最初からNULLを0に変換してデータベースへ登録する
  • 推奨しない方法: NULLを許可しておいて集計時にNULLIF関数やIS NOT NULL述語で排除する方法
  • 経験則: NULLを0に吸収させて問題化したことはあまりない
  • 概念的な違い: 「ガソリンタンクを持っていない車と、空のガソリンタンク」は概念的に異なるため、厳密には乱暴な方法

■ 12. 数値の現実的な対応

  • 基本方針: 0に変換する
  • 例外処理: どうしても0とNULLを区別したい場合だけ、NULLを許可する
  • 期待: 0に変換することで全てうまくいくことを祈る

■ 13. 日付の場合の対処法

  • 判断の必要性: NULLの持つ意味合いが多岐にわたるので、その場その場でデフォルト値を使うか、NULLを許可するかの判断が必要
  • 期限を指示する場合: 「0001-01-01」や「9999-12-31」のように可能な最大値・最小値を使う
  • 具体例: 社員の入社日やカードの有効期限など
  • 昔からの方法: この方法は昔からよく使われている

■ 14. 日付が未知の場合の対処法

  • 該当ケース: 歴史上の事件が起きた年月や誰かの誕生日など、コッドの分類における「未知」のNULLに相当する場合
  • デフォルト値の困難性: 意味ある値を入れることはできない
  • 対応オプション:
    • NULLを許可する
    • 「UNKNOWN」という文字列を入れる
    • 日付の列を文字型で宣言する(NOT NULL制約を確実に付けられる場合は日付型でも可)
  • 型変換の活用: 文字型と日付型は簡単に型変換できるため、暦日計算の場合だけキャストする選択も性能的なオーバーヘッドが問題にならなければ考慮に値する

■ 15. まとめ

  • NULL撲滅の基本方針:
    • まずデフォルト値を入れられないか検討する
    • どうしようもない場合だけNULLを許可する
  • 効果: これだけでもかなりNULLのもたらす厄介事からシステム開発を解放できる
  • 継続的改善: 「そのやり方ではうまくいかない」「もっと良い方法がある」という場合は、フィードバックを歓迎