nullについて最後まで考える(1) --- T字形で消せるnull,消せないnull

正美氏とかDateの本を読むと、Codd論文を理解するとnullの存在が許せなくなるのだなあ、と思う。
私はこのnull徹底排除の感覚が理解できていない。Codd論文読んだことないし。

比較演算や集計演算の対象に null が混入していると、直感に反する結果が出てくることがあるのはわかる。
同僚がnot null制約を付けていないのを見つけると「付けないとだめっすよ」と言ったりもする。
でも、これはわかった振りをしているだけなのだ。

  • nullがよくないのは当たり前として、あらゆるアプリケーションでnullを発生させないことができるのだろうか?
  • どうしても発生するケースがあるのだとすれば、どのnullはあってもよくて、どのnullはあってはならないのか?
  • where句に出現するnullと、サブクエリが返すnullと、アプリケーションに返されるnullと、罪の重さは全部同じなのか(違うんじゃないのか)?

を考え抜いていない。
今回は極力nullを減らす方法について考えて、次回、それでも発生するnullをどう捌くかを考えてみる。

ライフサイクルの違うデータを1個のテーブルに収容するから null が発生する

T字形ER手法のアウトラインを知って、今まで不可抗力だと思っていたnullの発生は、設計の問題 -- 具体的には、ライフサイクルの違うデータを1個のテーブルに収容するから -- だと分かった。
実戦でT字形の対照表やVEを援用すると、けっこうな数の null が消滅してしまった。

対照表でnullを消去

社員マスタに所属部門コードを埋め込んでしまうと、所属未定の社員の所属部門コードがnullになってしまう。

社員マスタ = { 社員コード, 社員名, ..., 所属部門コード }

これは社員マスタというresourceに配属というeventが混入しているから起きること。
この場合、対照表「社員.部門」を生成して、社員マスタから所属部門コードを排除する。
すると null を使わなくても、社員.部門に対応するレコードがないことをもって、「その社員に配属先がない」ことを表現できる。

社員マスタ = { 社員コード, 社員名, ... }
社員.部門 = { 社員コード, 所属部門コード }
VEでnullを消去

社員だけが使用できる社内シテスムがあるとする。
社員マスタにログインIDを埋めてしまうと、退職社員のレコードにnullが発生する(nullを入れなかったら、別の問題が発生する)。

社員マスタ = { 社員コード, 社員名, ログインID, パスワード, 削除フラグ }

前提として、退職社員のレコードを社員マスタから物理削除できない(他のテーブルから社員コードが参照されているため)ものとする。
また、ログインIDは重複を許したくないものとする。
この場合、退職した社員のログインIDの値をどう扱ったらよいか。


a. 何もしない
社員を論理削除して(=削除フラグを立てて)、ログインIDは放置する。
この場合、退職社員のログインIDを、新入社員が使えない。新入社員の田中AさんのログインIDに 'tanaka' を使いたくても、3年前に退職した田中Bさんが'tanaka'を占有しつづけているので、使えない。


b. 社員を論理削除した時に、ログインIDを空文字列にする
このアプローチでは、ログインIDに UNIQUE 制約を付けられない(=アプリケーションが重複チェックをしなくてはならなくなる)ので、やりたくない。


c. 社員を論理削除した時に、ログインIDに適当な文字列を入れる
削除日時のタイムスタンプを文字列化して書き込むとか。できるけど、やらないよなあ。


d. 社員を論理削除した時に、ログインIDにnullを充填する
仕方がないのでnullを使う。nullにしておけばログイン画面から入力されたログインIDと絶対一致しないので、上記3つのやり方より無難な感じがする。
これが一番ましかなあ。nullだけど、いいのかなあ。


これは、「社員情報」と「ログインID・パスワード」という、寿命の異なるデータを一緒に社員マスタに収容しているから発生する悩みだ。
ここでログインIDとパスワードを社員マスタから切り出し、VE「ログインアカウント」として独立させてみる。
ついでに「削除フラグ」は用済みなので廃止する。

社員マスタ = { 社員コード, 社員名 }
ログインアカウント = { 社員コード, ログインID, パスワード }

社員が退職したらログインアカウントからレコードを物理削除する。
これで、nullを使わなくても、その社員がログインできないことを表現できる。*1

それでも消えない null

上記のような措置により属性のライフサイクルを揃えただけでは、 null は撲滅できない。
「入力が任意の項目」「データ入力時点では不明・未定な情報」というのがあるから。
例えばECサイトの住所入力欄はたいてい

  • 市区町村
  • 番地
  • 建物

みたいに分かれているが、一戸建ての人のために「建物」の入力は任意になっている。
「建物」の入力がなかった時、データベース上「建物」の列には何を書くべきか。
nullは何かやばいってんで空文字列を書けばいいのか。
数値項目が未入力だったら何を書けばいいのか。0とか-1とかを勝手に書けばいいのか。それって別の問題を呼ばないか。
空文字列はともかく、0や-1は「集計や比較の結果を狂わせる」という意味で、nullと同じぐらいやばいんじゃないのか
... ということを、次回考える。

*1:※正しいT字形ER手法では「現役社員」「退職社員」をサブセットとして実装するのではないかと思う。が、いまだにサブセットが理解できないので、ここではVEを「乱用」してみる。