-
著者は DBMS のユーザに対し、ベンダがサポートしている 3VL を完全に無視し、そのかわりに統制された「デフォルト値」スキーマ(2値論理にしっかりと根ざしている)を利用することを薦める。……言い換えれば NULL と 3VL は関係モデルの全体的な基盤を害しているのである。
C.J.デイト[1]
このコラムでは、3値論理についての基礎的な知識の整理と、それがもたらす厄介な問題群の紹介を行ないます。これらの災厄に対抗するための設計方針やSQLの書き方など実践的な話は、「NULL撲滅委員会」にまわすので興味ある方はこちらも参照ください。ただ、念のためもう一度繰り返します。テーブル設計のときは可能な限り NOT NULL制約をつけてください。
二つのNULL、3値論理か、それとも4値論理か
3値論理の話を始めるに当たり、まず NULL についての話をしようと思います。なぜかというと、NULL こそが3値論理の元凶だからです。3値論理は、私たちにとって決して馴染み深い体系ではありません。高校までに習う数学では2値論理が採用されていますし、関係モデルの基礎理論の一つである述語論理も2値論理を原則とし、計算機もブール代数という2値論理に基づいて動作します。それなら、なぜリレーショナル・データベースの世界でのみ、3値論理という奇妙な体系が幅を利かせているのでしょう?
それはひとえに NULL のせいです。
この奇妙なトークンの存在を許したことで、関係モデルは true でも false でもない、unknown という第三の真理値を、つまりは3値論理を導入せざるを得なくなりました。それどころか驚かないでください。関係モデルの創始者コッドは、NULL をさらに2種類に分け、4値論理を採用することを提唱していたのです[2]。
コッドが言う二つの NULL とは何でしょう。それは「未知(UNKNOWN)」(サングラスをかけた人の眼の色)と「適用不能(Not Applicable)」(自動車の眼の色)です[3]。サングラスをかけた人の眼の色を知ることはできません。その人が何色かの眼の色を持つことは間違いありませんが、その色が何色かまでは分かりません。つまり、(1)ある個体が存在し、(2)その個体がある属性を持つことも分かっているが、具体的にどのような値であるかを知らない、という場合です。これが一つ目の NULL(未知)です。一方、「自動車の眼の色」という概念は、そもそも無意味です。「丸い正方形」や「長方形の体積」という概念と同じように。自動車に眼はないのだから、その色を問うことはできません。つまり、この場合は、(1)ある個体が存在し、(2)しかしその個体はある属性を持っていない、ということです。これが二つ目の NULL(適用不能)です。
コッドによる「失われた情報」の一覧表を引用しますので、参考にしてください。
RDBMS における失われた情報の分類[4]
現在の全ての DBMS の実装では、この二つの区別は行わず、ひとくくりにして「NULL」という特殊記号で表現しています。これは歓迎すべきことだと、私は思います。3値論理でさえ十分難しいのに、 NULL を二つに分けて4値論理を採用した日にはどれほど複雑な DBMS が出来上がるか知れたものではありません。
それでは簡単な練習問題です。次の表の1~4に、未知(UNK)か適用不能(N/A)を入れてみてください。解答はここです[5]。
個体名 | 性別 | 重さ | 虫歯の本数 |
---|---|---|---|
山田太郎 | 男 | 80kg | 1 |
自動車 | 2 | 120kg | N/A |
ドラえもん | N/A | UNK | 3 |
ミック | 男 | 4 | UNK |
なぜ「= NULL」ではなく「IS NULL」と書かなくてはならないのか?
これは、気になっている人も多いはずです。まだ SQL に不慣れな頃、ある列が NULL である行を選択しようとして、
SELECT *
FROM table_A
WHERE col_1 = NULL;
というクエリを書いてしまい、エラーになったり思い通りの結果が得られなかった、という経験は、ほぼ全ての人が持っているでしょう。ちょうど C言語や JAVA を習い始めのころに「if (a = 5)」と書いてしまう間違いとよく似ています。最初は、言語仕様の汚さにぶつぶつ文句をいいながらも、そのうち「IS NULL」という書き方に慣れてしまって、疑問を持たなくなります。
でもどう考えても奇妙な書き方ですよね。こんな素直でない書き方をしなくてはならないということには、やはりそれなりの理由があるのです。今からその理由を説明します。特に難しいことではありません。これでもう、「IS NULL」の違和感に悩まされることはありません。
理由は、NULL に「=」を適用した結果が必ず unknown になるからです[6]。一方、「=」という比較述語を使って WHERE句を作った場合、選択対象となる行は、「a = b」の判定結果が true になる行だけです。false や unknown はダメです。そして「a = b」の結果が true になるのは、a と b がともに NULL でなく、かつ同じ値であるときに限ります。
もし仮に a か b のどちらか一方でも NULL の場合、結果は unknown になります(もちろん a, b 両方が NULL の場合も unknown になります)。したがって、「col_1 = NULL」の結果は、col_1 の値によらず true にはならず、クエリはめでたく失敗、一行も選択できないか、DBMS によってはエラーを返すこともあるでしょう。結局、当該の列が NULL であるか否かを調べるために「=」を使うことはできず、別の述語「IS NULL」を用意する必要が生じるのです。
では何故「col_1 = NULL」の結果が絶対に true にならないのか? その理由は、そもそも NULL が値でも変数でもないからです[7]。だから、「=」のような値と変数にしか適用できない比較述語の入力項に、NULL を用いることは不可能なのです。
「NULL が値じゃない? そんな馬鹿な。おまえの言うことは信用ならん」という人のために、コッドとデイトの言葉を引用して権威付けしておきましょう。
-
私たちは、まず「失われてはいるが、適用可能な値」を示すマークの定義から始めよう。これを「A-Mark」と呼ぶ。このマークは、DBMS において値(value)としても変数(variable)としても扱われない。[8]
-
NULL に関する大切なことは、厳密には NULL が値ではないということである。[9]
unknown、第三の真理値
前章で初めて unknown という真理値が登場しました。いよいよこの3番目の真理値についてお話するときが来たわけです。最初に私が言ったことを覚えていますか? 「unknown は、関係モデルが NULL の存在を許したことで持ち込まれた"第三の値"」です。比較述語以外の述語に NULL を適用した場合にも、戻り値が unknown になる可能性があります。特に、IN述語と EXISTS述語の入力項に NULL が含まれる場合は、SQL のコーディングに大きな混乱をもたらします。この問題については次章で述べます。
ともあれ、こうして true(真)、false(偽)、unknown(不定)の三つの真理値に基づく論理が、関係モデルに持ち込まれたのですが、ここでちょっと注意してほしいことが一つあります。それは、真理値の unknown と NULL の一種である UNKNOWN は、全く別物であるということです。前者は真理値型のれっきとした値ですが、後者は値でも変数でもありません。区別しやすいように、前者を斜体の小文字で unknown、後者を普通の大文字で UNKNOWN と表記します[10]。両者の違いを理解するには、「x = x」という単純な恒等式を例に取るのが分かりやすいでしょう。x が真理値の unknown である場合、「x = x」は true に評価されます。一方、x が NULL の UNKNOWN の場合、この式は unknown に評価されます。
では、3値論理における論理演算の結果がどうなるか、一覧表を作って見てみましょう。太字の箇所が2値論理にはない、3値論理独特のものです。
x | NOT x |
---|---|
true | false |
unk | unk |
false | true |
|
|
SQL の他の述語は、NOT, AND, OR の三つの結合子を組み合わせることで表現できます。従って、これら論理述語の演算結果さえ定義しておけば、他の述語の演算結果も全て決定されます。ですから、上に示した三つのテーブルは、SQL の論理演算の基礎であることを肝に銘じてください。次章で、INと EXISTS の NULL に対する奇妙な振る舞いを検証します。一見、直観に反する振る舞いを見せるため、難しく感じるかもしれませんが、上のマトリックスを参照しながら読むと理解しやすいでしょう。
では本章の理解度をチェックするための練習問題です。解答はここ[11]。
a = 3, b = 4, c = NULLとする。このとき、次の式の真理値を答えよ。
1. a > b AND b > c
2. a > b OR b > c
3. a < b OR b < c
4. NOT( a = c)
あれほど NULL を許すなと言ったのに君も話の分からない奴だな。
NULL を許した列に IN述語や EXISTS述語を適用すると、戻り値として unknown が生じ、思っても見なかった結果に面食らうことがあります。多くのプログラマにとって、3値論理は扱いづらい体系です。その理由は、3値論理の計算が複雑なことと、学生時代に教わった数学や論理学が全て2値論理に基づいているので、2値論理の考えに慣れきってしまっていることです。
簡単な例を出しましょう。
SELECT * FROM SomeTable WHERE SomeColumn = 2 ・・・・・・(1)
SELECT * FROM SomeTable WHERE SomeColumn <> 2 ・・・・・・(2)
という二つのクエリを実行したとします。これで SomeTable の全ての行を選択できるように思えますが、そうではありません。
SELECT * FROM SomeTable WHERE SomeColumn IS NULL ・・・・・・(3)
という「第3のクエリ」を実行する必要があります。(もちろん、SomeColumn に NOT NULL制約が付いていれば(3)は必要ありません)。つまり、3値論理においては、「ある列は2か、2でないかのどちらかだ」という2値論理の大原則(排中律)が通用しないのです。
あるいは、社員の給料とボーナスの合計を求める以下のクエリは、salary 列に NULL が含まれている社員がいた場合、哀れその社員の「給料 + ボーナス」は NULL になってしまいます。
SELECT name, salary + bonus
FROM Employee
理由は簡単で、NULL に「+」を適用した結果が常に NULL になってしまうからです。この予想外の結果に悩まされたくなければ、
SELECT name, salary + bonus
FROM Employee
WHERE salary IS NOT NULL
として、NULLの行を除外するか、
SELECT name, COALESCE(salary, 0) + bonus
FROM Employee
として、加算の前に NULL を0に変換しなければなりません(くどいですが、最良の回避方法は、設計の時点であらかじめ salary列に NOT NULL制約を付けておくことです)。
簡単な SQL でさえ、このように直観と一致しないのだから、IN や EXISTS でサブクエリを使う複雑な SQL において、NULL がどれほどの暴れっぷりを見せるか、推して知るべし、です。
NULLと NOT IN述語
それではまず NOT IN において NULL がもたらす問題を見ましょう[12]。サンプルとして、以下のような簡単なテーブルを使います。Table2 の2行目の NULL に注目してください。
|
|
SLECT *
FROM Table1
WHERE x NOT IN (SELECT y FROM Table2);
というクエリを実行した場合、結果はどうなるでしょうか。答えは空、つまり一行も返されません。「あれ?」と思った方もいるでしょう。直観に従えば、3 と 4 が返るように思われます。Table2 に NULL が含まれていなければ、結果は確かに 3 と 4 になります。ですが、NULL がこのクエリを狂わせています。以下、実行ステップを順に見てみましょう。
1. サブクエリ「SELECT y FROM Table2」を実行
SELECT *
FROM Table1
WHERE x NOT IN ( 1, NULL, 2 );
2. NOT IN を同値変換
SELECT *
FROM Table1
WHERE NOT ( x IN ( 1, NULL, 2 ));
3. IN を OR で展開
SELECT *
FROM Table1
WHERE NOT (( x = 1 ) OR ( x = NULL ) OR ( x = 2 ));
4. ド・モルガンの法則を適用して同値変換
SELECT *
FROM Table1
WHERE (( x <> 1 ) AND (x <> NULL) AND ( x <> 2 ));
5. 「x <> NULL」は x の値によらず常にunknown に評価される
SELECT *
FROM Table1
WHERE (( x <> 1 ) AND unknown AND ( x <> 2 ));
6. 「( x <> 1 ) AND unknown AND ( x <> 2 )」は全ての x についてunknown または false になる
SELECT *
FROM Table1
WHERE unknown;
どうでしょう。段階を追うことでこの一見おかしな振る舞いの原因が NULL にあることがお分かりいただけたでしょうか。NOT IN のサブクエリで使用されるテーブルのキーに NULL が含まれている場合、結果は必ず空になるのです。
では次の例を見てください。今度は逆に、Table1 に NULL の行がある場合です。同じクエリを投げると、今度は直観に反することなく、結果は1行で 4 が返ります。NULL の行は返されないことに注意しましょう。
|
|
ではこちらの方も、クエリの実行ステップを段階的に追います。
1. サブクエリ「SELECT y FROM Table2」を実行 SELECT * FROM Table1 WHERE x NOT IN (1, 2, 3); 2. NOT INを同値変換 SELECT * FROM Table1 WHERE NOT (x IN (1, 2, 3)); 3. INをORで展開 SELECT * FROM Table1 WHERE NOT ((x = 1) OR (x = 2) OR (x = 3)); 4. ド・モルガンの法則を適用して同値変換 SELECT * FROM Table1 WHERE ((x <> 1) AND (x <> 2) AND (x <> 3)); 5. Table1の各行について、xに値を代入する。UNION ALLで表現する。 SELECT * FROM Table1 WHERE ((1 <> 1) AND (1 <> 2) AND (1 <> 3)) UNION ALL SELECT * FROM Table1 WHERE ((2 <> 1) AND (2 <> 2) AND (2 <> 3)) UNION ALL SELECT * FROM Table1 WHERE ((NULL <> 1) AND (NULL <> 2) AND (NULL <> 3)) UNION ALL SELECT * FROM Table1 WHERE ((4 <> 1) AND (4 <> 2) AND (4 <> 3)) 6. 比較述語の計算を行い、真理値の定数に変換する SELECT * FROM Table1 WHERE (false AND true AND true) UNION ALL SELECT * FROM Table1 WHERE (true AND false AND true) UNION ALL SELECT * FROM Table1 WHERE (unknown AND unknown AND unknown) UNION ALL SELECT * FROM Table1 WHERE (true AND true AND true) 7. 真理値の計算を行い、WHERE句の条件を定数にする SELECT * FROM Table1 WHERE false UNION ALL SELECT * FROM Table1 WHERE false UNION ALL SELECT * FROM Table1 WHERE unknown UNION ALL SELECT * FROM Table1 WHERE true 8. WHERE句がtrueとなる1行( x = 4 )のみが選択される。
NULLと NOT EXISTS述語
次に NOT EXISTS において NULL がもたらす問題を見ます。サンプルとして、以下のテーブルを使います。トーバルズの誕生日が NULL(「未知」の NULL です!)であることに注目してください。
|
|
このテーブルに対し、有名人と同じ日に生まれなかった従業員を全て選択する、次のようなクエリを発行します。さて、結果はどうなるでしょうか?
SELECT P.name FROM Personnel P WHERE NOT EXISTS (SELECT * FROM Celebrities C WHERE P.birthday = C.birthday);
結果:二太とかのこの二人が選択される。
解説:一太が選択されないのは当然です。一太はストールマンと誕生日が一致するからです。では二太とかのこが選択されるのはなぜでしょう? 二人は確かにストールマンとレイモンドとは誕生日が一致しません。問題は、トーバルズの誕生日との比較です。トーバルズの誕生日は NULL です。従って、この比較は以下のようなステップを推移します。
1. サブクエリにおいて、トーバルズの誕生日(NULL)との比較を行なう SELECT P.name FROM Personnel P WHERE NOT EXISTS (SELECT * FROM Celebrities C WHERE P.birthday = NULL); 2. NULLに比較述語を適用した結果は必ず unknown になる SELECT P.name FROM Personnel P WHERE NOT EXISTS (SELECT * FROM Celebrities C WHERE unknown); 3. サブクエリは結果を返さないので、反対に NOT EXSITS の評価結果は true になる SELECT P.name FROM Personnel P WHERE true;
誕生日が未知のトーバルズは、誰とも誕生日が一致しない人物として扱われるのです。この点に違和感を覚える人もいるでしょう。確かに現在のところ彼は誕生日を公開していないかもしれない。でもいつの日か新聞に誕生日が載るかもしれない。だから彼は全員と誕生日が一致する人物として扱われるべきではないか、と。しかし残念ながら、現在の RDBMS において、その直観は保証されません。
NOT IN述語と NOT EXISTS述語は同値ではない
よく使われるパフォーマンス・チューニングのテクニックとして、IN を EXISTS で書き換えるというものがあります。これは全く害のない方法です。注意すべきは、NOT IN と NOT EXISTS を書き換える場合には、必ずしも互換性がないことです。「必ずしも」という条件を付けたのは、結合キーとなる列に NULL がなければ、NOT が無いときと同様、安全に書き換えられるからです。またもや、NULL が問題をもたらします。例えば、前章で例として使った NOT EXISTS を NOT IN で書き換えるとこうなります。
--1. 有名人と同じ日に生まれなかった従業員を全て選択するクエリ SELECT P.name FROM Personnel P WHERE NOT EXISTS (SELECT * FROM Celebrities C WHERE P.birthday = C.birthday); --2. 1と同じ結果を返すクエリ??? SELECT name FROM Personnel WHERE birthday NOT IN (SELECT birthday FROM Cerebrities);
直観はここでも裏切られます。1番のクエリが二太とかのこの二人を選択することは、前章で見た通りです。一方、2番のクエリは一行も選択しません。理由はもう説明不要でしょう。
以上で、NOT IN とNOT EXISTS でサブクエリを作る際に NULL がもたらす問題を見てきました。プログラミングの際に直観に頼ることができないというのは困難な条件ですが、DBエンジニアはこの奇妙な現象について知っておく必要があります。そして最後に、NULL の問題について最も大切なことを繰り返すなら、テーブル設計の段階で可能な限り NULL を排除すること、です。
神のいない論理
最後に、データベースから離れて論理学について書かせてください。実務以外に興味はない、という方は強いて読む必要もないのですが、きっと面白いはずです。
3値論理学(three-valued-logic)を史上初めて作り上げたのは、ポーランドを代表する論理学者 J.ウカシェヴィッツ (1878-1956)です[13]。1920年代、彼は「真」でも「偽」でもない第三の真理値「可能」を定義しました。
なぜこの時期に3値論理が誕生したかというと、1920-30年代というのは、論理学において古典論理批判が始まった「革命の時代」だったからです。3値論理以外にも、ブラウワーやハイティンクらによる直観主義論理学も創始され、ここから ―― 19世紀後半までの沈滞ムードを吹き払うように ―― 絢爛たる非古典論理が華開いていきます。
古典論理において特に批判の対象になったのが、排中律(A ∨ ¬A)という公理の一つと、それを支える2値原理でした。排中律とは「A か A でないかは常に成り立つ」ことを意味する公理で、2値原理は「ある命題は必ず真か偽に定まる」という前提です。2値原理は、私たち人間にとっては決して受け入れやすい前提ではありません。この世の中には真偽の分からない命題はそこらじゅうに転がっているではありませんか。例えば、「神は存在する」とか「殺人は悪だ」とか「6 より大きい全ての偶数は二つの奇素数の和で表せる」(ゴールドバッハの予想)とか。古典論理は、こうした疑問に対してこう答えます。「でも神様なら全ての命題の真偽を知っているはずだ。」 神様なら、どんな難しい計算も瞬時に解き、宇宙開闢以来の全歴史に精通し、お望みならタイムスリップだってできます。何しろ全知全能ですから。古典論理が「神の論理」と呼ばれる所以です。
一方、そのような人間からかけ離れた論理学ではなく、人間の有限な認識をなるべく忠実に反映する論理学があるべきではないか、と考える人々がいます。そういう論理学においては、命題の真理値は「真」と「偽」だけではない、「無意味」とか「今のところ不明」とか「矛盾」とか、様々な認識を反映する値がありえるでしょう。こうして、3値論理が生まれ、さらに3値以上の真理値を認める多値論理学(many-valued logic)の研究がスタートしていくことになります。神のいない論理 ―― あるいは人間の論理 ―― の誕生です。
データベースを扱う主体は、当然ながら、神様ではなく人間です。それゆえ、情報の表現方法も、神様の完全無欠な認識ではなく、有限で不完全な人間の認識を尊重しようという発想に基づいています。ここに3値論理が用いられたわけです。しかし、この人間志向的な思想は諸刃の剣でした。確かに、3値論理を(要するに NULL と unknown を)採用することで、リレーショナル・データベースは非常に人間の認識に近い、柔軟な表現力を獲得しました。しかしそれによって、皮肉にも、人間の直観に反する奇妙な論理計算をも導入しなければならなかったのです。
注
[1] C.J.デイト『データベースシステム概論 第6版』p.636
[2] Codd,E,F, 1990, "The Relational Model for Database Management: Version 2" p.182
私は、コッドが4値論理を提唱していたことを知ったとき、控えめに言ってぶったまげました。そして、4値論理がどのベンダーの興味も惹かなかったことを、全てのDBエンジニアにとって喜ばしく思いました。
参考までに、4値論理における真理値表をご覧にいれましょう。UNKNOWN の NULL のための真理値として applicalble が、Not Applicable の NULL のための真理値として inapplicable が導入されています。あくまで参考ですよ。いくら大恩あるコッド博士の言うこととはいえ、こんなもの本気で受け取ってはなりません!
|
|
|
例としてベルナップの4値論理を見ましょう。彼によれば、知識ベースには次のような4つの認識状態が存在することになります。
true :命題は真である
false:命題は偽である
none :命題は真でも偽でもない
both :命題は真であり偽である
ここで、true と false は、古典的な2値論理から存在する真理値です。none は3値論理で言えば unknown に相当します。both が4値論理特有の、矛盾認識を表現する真理値です。
[3] Codd 1990, p.170
コッドは、「未知」の NULL を「A-Mark」、「適用不能」の NULL を「I-Mark」と呼んでいます。A とか I というのは、「未知」の NULL は属性を適用可能(Applicable)だが、「適用不能」の NULL は属性を適用できない(Inapplicable)ことから来た呼び方です。
[4] Codd 1990, p.171
[5] 1.UNK:山田太郎は人間なので、「虫歯になる」という属性を持ちます。しかし、具体的に何本かまでは分かりません。
2.N/A:自動車には「性別」という属性が適用できません。
3.N/A:ドラえもんはロボットなので、「虫歯になる」という属性は適用できません。
4.UNK:私(ミック)は人間なので当然「体重」という属性を持ちます。しかし何キロかは皆さんには分かりません。
[6] 「=」に限らず、「<」「>」「<>」など比較述語の入力項が NULL の場合、結果は常に unknown です。a の値が分からなければ、b の値にかかわらず(そしてまた b の値が分からなければなおさら) a > b であるかどうかは明らかに分かりません。
[7] 値ではないので、NULL は当然、定義域にも含まれません。NULL をデータの値であると勘違いしている人は、ここでしっかりとそういう誤った考えを捨てておきましょう(逆に聞きたいのですが、もし仮に NULL が値であるとすれば、その型は一体何なのですか?)。きわめて頻繁に使われる「NULL値」、「ナル値」、「列の値が NULL である」という言い方も、全くの間違いです。
関係モデルにおける NULL を値と勘違いしやすい理由の一つは、おそらく、C言語や Java において NULL が一つの値(定数)として扱われている(大半の処理系では0の別名に過ぎない)ため、それと混同しがちなことでしょう。
歴史的には、1989年、コッドが「SQL の致命的な欠点(Fatal Flaws in SQL)」という論文を発表し、関係モデルでは NULL を値として扱えないことを認めました。同様の内容を "The Relational Model for Database Management: Version 2" で読むことができます。
[8] Codd 1990, p.173
[9] デイト, p.619
[10] この表記は、デイトの『データベースシステム概論 第6版』に従いました。この表記方法が一般的なのかどうかは、私には分かりません。セルコは『プログラマのためのSQL 第2版』で真理値を大文字の TRUE、FALSE と表記していますし、あまり統一はないようです。
[11]
1. false
2. unknown
3. true
4. unknown
[12] この例は、セルコ『プログラマのためのSQL 第2版』p.184-86から借用しました。
[13] ウカシェヴィッツは、タルスキ、レスニエフスキらと一緒に「ワルシャワ学派」の中心的存在だった論理学者です(Lisp に応用されているポーランド記法を考案したのもこの人)。以下に彼の3値論理のマトリクスの例を挙げます。「可能(0.5)」という真理値があるのが特徴です。
|
|
ウカシェヴィッツの3値論理に関しては、戸田山和久『論理学をつくる』p.284 が簡明な説明を与えてくれます。
Copyright (C) ミック
作成日:2002/12/01
最終更新日:2017/06/22 Tweet