[DBデザイン#37] NULLについて

この話はもう少し前にしたほうがよかったですね。何かと話題のNULLについてです。

まず、NULLあるいはそれに近い概念はプログラミング言語で扱われていて、それはそれで必要だから実装されていることもあって是非の議論はされていませんが、データベースでは是非論は検索すればいろいろ出てくるかと思います。

ですが、そのコアな議論の前に、日本での発音についてまずは指摘したいです。Wikipediaによると、「ヌル」あるいは「ナル」と呼ばれているとされていますが、実際にはヌルと呼ぶ人が多いです。アメリカ人の発音を聞くと、「ナラ」って感じで、ラは正確にはLの発音だけでからナのAが影響してラとも聞こえます。ただし、日本語的に妥協するとラよりルですね。nu*は、nutsとか、numberとか、大体ナではないかと思います。なのに、日本人だけローマ字読みしてヌと言ってしまっている。もちろん、日本人は正確には発音できないからなんでもいいということも言えなくもないのですが、ヌルはちょっと遠すぎませんか、せめてナルかナラあたりで発音しておくのがいいかと思います。

データベースでは、フィールドを定義するとき、型を指定するのが一般的ですが、どんな型であっても、その型にはまった値以外にNULLという値を設定できるようになっています。また、一般には初期値はNULLになっています。ここで、NULLという値は取らないという設定、そして実際にNULLを入力しようとするとエラーを出して入力できないようにするという機能もあり、設計者が自由にNULLに対する挙動はセットアップできるのが今時のデータベースです。ただ、メンテナンス開発などでは、「ここはNULLにならないようにした!」と思っても、データベースの設計変更はしてはならないという掟があって、そのままの状態でデータベースを使って改変しないといけないかもしれあません。

NULLは論理式の中に存在すると、普通のデータとは挙動が異なります。式の中に1つでもNULLがあると、結果はNULLになると考えれば良いでしょう。NULL自体は論理値として判定するとFALSEになります。すると、FALSE = NULL という論理式の結果はNULLが入っているので自動的にNULLになります。右辺はNULLだからFALSEと思ってしまうと、FALSE = FALSEの結果がFALSEになってしまって、ちょっとおかしいなということになります。簡単な式なので、まあ普通はわかるだろうと思いがちですが、複雑な式の一部だとなかなかわかりにくいです。このように、式の判定にNULLが絡むと思った通りの結果にならないということもあって、ある値がNULLかどうか、NULLでないかを判定するための演算子があります。「field IS NULL」「field IS NOT NULL」のように記述します。右端のNULLが値のように見えますが、正確にはIS NULLあるいはIS NOT NULLという演算子ということになっています。理想的にはNULLかどうかの判定はこれらの演算子で行い、NULLでない値だけで論理式を組み立てているくらいの対応が望まれます。別の人が作った論理式を理解しないといけないような場合で、NULLを加味しないといけない場合と、しなくていい場合を考えれば、後者の方がよほど楽で把握にミスが入りづらいと言えます。

そういうことで、まず、重要なことは、データベースシステムに関わる人は、NULLという特別な値になる可能性があるということを知っておく必要があります。フィールドの定義を見て、NULLにならない設定をしているフィールドとしていないフィールドを把握しましょう。文字列でも、数値でもそうなりますが、論理値でもNULLがあります。その結果、論理値なのに、TRUE/FALSE/NULLという3通りの値を取るということになってしまって、後で説明しますが、それも混乱しやすいところです。ちなみに、NULLは基本的に値がないことを示すものですが、「値がない」をNULLとする方法と、「値がないことを示す値」を使うを使う方法があり、後者だとNULLにならなくても良いとも言えます。ディレクトリサービスの世界では、値に対するNULLという概念がなく、値がないことを示すために非常に古い時代に「99」を使った経緯があります。システムによって「値がない」ことを示す数値や文字列は違いますが、どんな値を選択しても、その値がデータだったら…という危険性は常に付き纏います。そこで、NULLを使わないで値の有無を管理したい場合は、データのフィールドと、そのデータの有無を判定する論理値のフィールドの、2つのフィールドを使うのが正しい設計になります。そうなると、値の編集では2つのフィールドを処理しないといけなくなり、複雑さは増します。

データベースとのやりとりをプログラミング言語等で実装する場合は、フィールドの値がNULLだった場合の挙動がどうなるかをしっかり確認します。もちろん、それによって表示を切り替える必要があるかもしれません。NULLかどうかの判定は、どの言語もいろいろ事情があるので、安易な条件判定で失敗しがちです。ともかく、NULLに対峙するには、言語上でのNULLの扱いについて、まずはしっかり確認しましょう。文字列データは、比較的安直でいいかもしれません。画面上に「NULL」とか「null」と出てしまうのは言語道断としても(と言っても、時々見ますね)、NULLだと””にするあたりで大体解決するかもしれません。要するに、NULLと””を同じだと考えるということをシステム全体に渡って進めてしまうと、NULLを許可してもそんなに混乱はないと思われます。数値だと、NULLだと0でもいいのかもしれませんが、未入力であることを示すために意図的にNULLを使うということもあるでしょう。そうなると、やはりNULLであれば””と見るというような対処は必要です。いずれにしても、未入力という状態を扱うのかどうかということが、きちんと仕様に記載されていることが期待されます。一方、書き込みの場合がいろいろややこしいです。文字列や数値が確定していればそれを書き込めばいいのですが、逆にどういう場合にNULLにしないといけないのかということがきちんと仕様になっている必要があります。この辺り、きちんと仕様が記述されている必要があることながら、実際の開発ではかなり大雑把に進むことが多いという印象があります。おそらく多くの方は「当たり前」と判断しているのでしょうけど、解釈に若干の揺らぎがあるような領域では、当たり前では通じない可能性もあります。ですので、その「当たり前」を明文化するのが仕様書であることは言うまでもありません。いずれにしても、実データ以外にNULLを書き込む必要がある場合には、そのための処理は記述が必要になります。

概して、NULLが不要なら、NULLの保存ができないようにフィールド定義で指定するのが良いと言えます。例えば、あるフィールドはチェックボックスの処理と連動しているとしたら、論理値にしてNULLは許可しないようにします。そうしないと、TRUEはオンだけど、FALSEとNULLはオフのような解釈になって、何のために論理値を型として選んでいるのか意味がなくなってしまいます。ただ、このオンがTUREだけなものはまだ良くて、文字列フィールドをチェックボックスに割り当ててしまい、ある画面ではオン/オフが1/0、別の画面ではYES/NULLのようなことになってしまうこともあり得ます。もちろん、バグなのですが、メンテナンス開発ではそれを直せないとしたら、この状態で、機能を組み込まないといけないかもしれません。そうなると、オンかオフかの単純な判定であっても、「field = 0 OR field IS NULL」のような複合条件になってしまいます。それに、そんな不一致があるシステムは2通りで済んでいるかどうかは怪しく、さらに調査して、判定式の変更も必要になるかもしれません。結果的に論理値で済むフィールドは、最初からNULLを禁止した論理値のフィールドとして定義しておくことで、メンテナンス性も高まるはずです。

照合するフィールドについて、外部キーに相当する方のフィールドについても、NULLの挙動を検討する必要があります。外部キーフィールドに「必ず対応する主キーのフィールドの値がなくてはいけない」という制限を付けることもでき、その場合はテーブル間の関連付けが常に何かのレコードに対して設定されていることを期待しているものです。その場合は、NULLでなく、加えて主キーの値のどれかが入っているということになります。一方、外部キーにNULLを許すと、対応するレコードがまだ未確定であるということを示すことができます。もちろん、未確定なのが困るということもあるかもしれませんが、ワークフロー上未確定になってしまう場合もあるので、一概に「必ず値を入れなければならない」ということでは済まされません。未確定で一定の期間過ぎてから、何か対応する値が設定されるというようなワークフローであれば、外部キーはNULLであっても良いというか、NULLの特徴をうまく利用できているとも言えます。

ここからはFileMakerの話です。FileMakerにはNULLはありません。その一方で、事実上””をNULLと見做すような実装がなされています。しかしながら、””は空文字列というデータである場合もあります。””は検索や、>等での比較はできますが、唯一、リレーションシップでの=の処理において、”” = “” の結果はfalseになります。つまり、照合データとしては””は使えないということです。何か、数値なり文字列なりが入ったフィールド同士でないと照合できません。言い換えれば、照合されないようにするには””を利用するという意味で、この場面だけNULL相当のデータが存在しています。

NULLの話は尽きないですが、とりあえずこの辺りで今日は終わりましょう。