複合主キーのnull許容列の何が問題になっていますか?


149

ORACLEでは、主キーを構成する列にNULL値を使用できません。同じことが他のほとんどの「エンタープライズレベル」のシステムにも当てはまるようです。

同時に、ほとんどのシステムでは、null許容列に一意の制約も許可されています。

一意制約にNULLを設定できるのに、主キーには設定できないのはなぜですか?これには根本的な論理的な理由がありますか、それともこれは技術的な制限ですか?


回答:


216

主キーは、行を一意に識別するためのものです。これは、キーのすべての部分を入力と比較することによって行われます。

定義ごとに、NULLを比較の成功の一部にすることはできません。それ自体との比較(NULL = NULL)も失敗します。つまり、NULLを含むキーは機能しません。

さらに、オプションの関係をマークするために、外部キーでNULLを使用できます。(*)これをPKでも許可すると、これは壊れます。


(*)注意:NULL可能な外部キーを使用することは、リレーショナルデータベースの設計をきれいにすることではありません。

2つのエンティティがAありBAオプションでに関連付けることができる場合B、解決策は解決テーブルを作成することです(たとえば、としましょうAB)。そのテーブルは以下とリンクABます。関係がある場合レコードが含まれ、存在しない場合は含まれません。


5
受け入れられた答えをこれに変更しました。投票で判断すると、この答えはより多くの人々にとって最も明確です。トニー・アンドリュースの答えがこのデザインの背後にある意図をよりよく説明していると私はまだ感じています。是非チェックしてね!
Roman Starkov、2015

2
Q:行がないのではなくNULL FKが必要になるのはいつですか?A:最適化のために非正規化されたスキーマのバージョンでのみ。重要なスキーマでは、このような非正規化された問題が原因で、新しい機能が必要になるたびに問題が発生する可能性があります。otoh、webデザインの群衆は気にしません。良いデザインのアイデアのように聞こえるようにする代わりに、少なくともこれに関する注意書きを追加します。
zxq9 2015

3
「null許容の外部キーを持つことは、リレーショナルデータベースの設計をきれいにすることではありません。」-nullのないデータベース設計(第6正規形)は常に複雑さを追加します。得られるスペース節約は、多くの場合、それらの利点を実現するために必要な追加のプログラマーの作業よりも重要です。

1
ABC解決テーブルの場合はどうなりますか?オプションのC付き
Bart Calixto

1
これは本当に何も説明しないので、「標準が禁止しているため」と書かないようにしました。
Tomalak

62

主キーは、テーブルのすべての行の一意の識別子を定義します。テーブルに主キーがある場合、そこから任意の行を選択する確実な方法があります。

ユニーク制約は必ずしもすべての行を識別するわけではありません。それだけであることを指定した場合、行はその列の値を持っている、そして、彼らは一意である必要があります。これは、すべての行を一意に識別するには十分ではありません。これは、主キーが行う必要があることです。


10
SQL Serverでは、NULL可能な列を持つ一意の制約により、その列の値「null」は1回だけ許可されます(制約の他の列に同じ値が指定されている場合)。したがって、このような一意の制約は、本質的にnull可能な列を持つpkのように動作します。
ジェラール

Oracle(11.2)についても同じことを確認します
Alexander Malakhov

2
Oracle(SQL Serverについては知りません)では、テーブルに多くの行を含めることができ、一意制約のすべての列がnullになります。ただし、一意性制約の一部の列がnullでなく、一部がnullの場合、一意性が適用されます。
トニーアンドリュース

これは複合UNIQUEにどのように適用されますか?
2014

1
@Dims SQLデータベースの他のほとんどすべてと同様に、「実装によって異なります」。ほとんどのデータベースでは、「主キー」は実際にはその下のUNIQUE制約です。「主キー」の概念は、UNIQUEの概念ほど特別で強力なものではありません。実際の違いは、一意であることを保証できるテーブルの2つの独立した側面がある場合、定義により正規化されたデータベースがないことです(同じテーブルに2種類のデータを格納します)。
zxq9 2015年

46

基本的に、複数列の主キーのNULLに問題はありません。しかし、これがあると、設計者が意図していない可能性が高いため、多くのシステムでこれを試行するとエラーがスローされます。

一連のフィールドとして保存されているモジュール/パッケージバージョンの場合を考えてみます。

CREATE TABLE module
  (name        varchar(20) PRIMARY KEY,
   description text DEFAULT '' NOT NULL);

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20),
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

主キーの最初の5つの要素は、リリースバージョンの定期的に定義された部分ですが、一部のパッケージには、通常は整数ではないカスタマイズされた拡張機能があります(「rc-foo」、「vanilla」、「beta」など、他の誰か誰4フィールド)がアップ夢かもしれませんが不十分です。パッケージに拡張子がない場合、上記のモデルではNULLであり、そのままにしておいても害はありません。

しかし、NULL とは何ですか?それは情報の欠如、未知のものを表すはずです。そうは言っても、おそらくこれはもっと理にかなっています:

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20) DEFAULT '' NOT NULL,
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

このバージョンでは、タプルの "ext"部分はNOT NULLですが、デフォルトでは空の文字列になります。これは、意味的に(そして実際的に)NULLとは異なります。NULLは不明ですが、空の文字列は「存在しないもの」の意図的な記録です。つまり、「空」と「null」は別物です。「ここに値がない」と「ここにある値がわからない」の違い。

バージョン拡張がないパッケージを登録すると、パッケージに拡張がないことがわかります。したがって、実際には空の文字列が正しい値です。NULLが正しいのは、拡張子があるかどうかがわからない場合、または拡張子があることはわかっていても、拡張子がわからない場合のみです。この状況は、文字列値が標準であるシステムで処理するのが簡単です。0または1を挿入する以外に「空の整数」を表す方法がないため、後で行われる比較でロールアップされます(これは独自の意味)*。

ちなみに、Postgresではどちらの方法も有効です(「エンタープライズ」RDMBSについて説明しているため)。ただし、NULL == "わからない"ため、比較結果にNULLを入れると、比較結果がかなり異なる場合があります。 NULLを含む比較の結果は、未知のものがわからないためNULLになります。危険!それについて注意深く考えてください。これは、NULL比較結果一連の比較を通じて伝播することを意味します。これは、ソート、比較などの際の微妙なバグの原因となる可能性があります。

Postgresは、あなたが成人であると想定し、この決定を自分で行うことができます。OracleとDB2は、愚かなことをしていることに気づかず、エラーをスローすると想定しています。これは通常は正しいことですが、常にそうであると限りません。場合によっては、実際にはわからずNULLになる場合があるため、意味のある比較が不可能な不明な要素を含む行を残すことは、正しい動作です。

いずれの場合も、スキーマ全体で許可するNULLフィールドの数を排除するように努力する必要があります。主キーの一部であるフィールドに関しては、二重にそうする必要があります。ほとんどの場合、NULL列の存在は、(意図的に非正規化されたものとは対照的に)正規化されていないスキーマ設計を示しており、受け入れる前に非常に注意深く考える必要があります。

[* 注:整数の和集合であるカスタムタイプと、「不明」ではなく意味的に「空」を意味する「ボトム」タイプを作成することが可能です。残念ながら、これは比較操作に多少の複雑さをもたらし、通常NULLは最初から多くの値を許可するべきではないため、通常は本当に型正しであることは実際に努力する価値はありません。とは言っても、「値なし」のセマンティクスを「不明な値」と偶然に混同する習慣を防ぐために、RDBMSにデフォルトのBOTTOMタイプが含まれていればすばらしいと思いますNULL]


5
これは非常に良い答えであり、NULL値について多くのことを説明しており、多くの状況での影響です。あなた、サー、今私の尊敬の念を持っています!大学でさえ、私はデータベース内のNULL値についてそのような良い説明を得ました。ありがとうございました!

私はこの答えの主要な考えを支持します。しかし、「情報の欠如を表すと想定される、不明」、「意味的に(そして実質的に)NULLとは異なる」、「NULLは不明」、「空の文字列は、「存在しないもの」の意図的な記録です。 「」、「NULL ==「わからない」」などは曖昧で誤解を招くものであり、実際に存在しないステートメントのニーモニックは、NULLまたは任意の値がどのように使用されるか、または使用されるか、または使用されることが意図されていたかについてのみです。 。(SQL NULL機能の(悪い)設計に影響を与えることを含みます。)それらは何も正当化または説明しません。彼らは説明され、暴かれるべきです。
philipxy

21

NULL == NULL-> false(少なくともDBMSでは)

したがって、実際の値を持つ追加の列があっても、NULL値を使用して関係を取得することはできません。


1
これは最良の答えのように聞こえますが、主キーの作成時にこれが禁止されている理由はまだわかりません。これが単に取得の問題である場合はwhere pk_1 = 'a' and pk_2 = 'b'、通常の値を使用して、where pk_1 is null and pk_2 = 'b'nullがある場合に切り替えることができます。
EoghanM 2013年

または、さらに確実に、where (a.pk1 = b.pk1 or (a.pk1 is null and b.pk1 is null)) and (a.pk2 = b.pk2 or (a.pk2 is null and b.pk2 is null))/
ジョーダンリーガー2013年

8
間違った答え。NULL == NULL->不明。間違いではない。問題は、テストの結果が不明の場合、制約に違反しているとは見なされないことです。これはしばしば、比較が偽をもたらすかのようにSEEMにしますが、実際にはそうではありません。
Erwin Smout、2015

4

トニー・アンドリュースの答えはまともです。しかし、本当の答えは、これはリレーショナルデータベースコミュニティで使用されている規約であり、必要ではないということです。多分それは良い慣習ではないかもしれません。

何かをNULLと比較すると、UNKNOWN(3番目の真理値)になります。したがって、ヌルで提案されているように、平等に関するすべての伝統的な知恵は窓の外に出ます。まあそれは一見それがどのように見えるかです。

しかし、これは必ずしもそうだとは思いません。SQLデータベースでさえ、NULLが比較の可能性をすべて破壊するとは考えていません。

データベースでクエリSELECT * FROM VALUES(NULL)UNION SELECT * FROM VALUES(NULL)を実行します

表示されるのは、値がNULLである1つの属性を持つ1つのタプルだけです。したがって、ユニオンはここで2つのNULL値を等しいと認識しました。

3つのコンポーネントを持つ複合キーを3つの属性(1、3、NULL)=(1、3、NULL)のタプルと比較すると、<=> 1 = 1 AND 3 = 3 AND NULL = NULL結果はUNKNOWNになります。

しかし、たとえば、新しい種類の比較演算子を定義することができます。==。X == Y <=> X = Y OR(XはNULLかつYはNULL)

この種の等価演算子を使用すると、nullコンポーネントを持つ複合キーまたはnull値を持つ非複合キーが問題なくなります。


1
いいえ、UNIONは2つのNULLを区別できないものとして認識しました。これは「等しい」と同じではありません。代わりにUNION ALLを試すと、2つの行が表示されます。「新しい種類の比較演算子」については、SQLにはすでにあります。から区別されません。しかし、それだけでは十分ではありません。NATURAL JOINなどのSQL構文、または外部キーのREFERENCES句でこれを使用するには、それらの構文にさらに追加のオプションが必要になります。
Erwin Smout、2015

ああ、アーウィン・スムート。このフォーラムでお会いできて本当にうれしいです!SQLの「IS NOT DISTINCT FROM」は知らなかった。とても興味深い!しかし、それはまさに私が作った==演算子で何を意味しているかのようです。「それだけでは十分ではない」とあなたが言う理由を教えてください。
Rami Ojares、2015

REFERENCES句は、定義により、平等に基づいています。(より厳密な)EQUALではなくNOT DISTINCTである対応する属性値に基づいて、子タプル/行を親タプル/行と照合する一種の参照は、このオプションを指定する機能を必要としますが、構文は許す。NATURAL JOINの同上。
Erwin Smout、2015

外部キーが機能するためには、参照先が一意である必要があります(つまり、すべての値が異なる必要があります)。つまり、単一のnull値を持つ可能性があります。REFERENCESがNOT DISTINCT演算子で定義される場合、すべてのnull値はその単一のnullを参照できます。私はそれが(もっと便利な意味で)良いと思います。JOIN(外部と内部の両方)では、左側のnullが右側のすべてのnullと一致するときに「NULL MATCHES」が乗算されるため、厳密な等号の方が良いと思います。
Rami Ojares、2015

1

私は今でも、これは技術によってもたらされた根本的/機能的な欠陥だと信じています。顧客を特定できるオプションのフィールドがある場合は、NULL!= NULLであるという理由だけでダミー値をハックする必要があります。特にエレガントではありませんが、「業界標準」です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.