Was ist falsch an nullbaren Spalten in zusammengesetzten Primärschlüsseln?

149

ORACLE erlaubt keine NULL-Werte in einer der Spalten, die einen Primärschlüssel enthalten. Es scheint, dass dies auch für die meisten anderen Systeme auf Unternehmensebene gilt.

Gleichzeitig erlauben die meisten Systeme auch eindeutige Einschränkungen für nullfähige Spalten.

Warum können eindeutige Einschränkungen NULL-Werte haben, Primärschlüssel jedoch nicht? Gibt es dafür einen fundamentalen logischen Grund oder handelt es sich eher um eine technische Einschränkung?

Roman Starkov
quelle

Antworten:

216

Primärschlüssel dienen zur eindeutigen Identifizierung von Zeilen. Dazu werden alle Teile eines Schlüssels mit der Eingabe verglichen.

Per Definition kann NULL nicht Teil eines erfolgreichen Vergleichs sein. Auch ein Vergleich mit sich selbst ( NULL = NULL) wird fehlschlagen. Dies bedeutet, dass ein Schlüssel mit NULL nicht funktioniert.

Zusätzlich ist NULL in einem Fremdschlüssel zulässig, um eine optionale Beziehung zu markieren. (*) Wenn Sie es auch in der PK zulassen, wird dies beschädigt.


(*) Ein Wort der Vorsicht: Nullfähige Fremdschlüssel sind kein sauberes relationales Datenbankdesign.

Wenn es zwei Entitäten gibt Aund Bauf Adie optional Bezug genommen werden kann, Bbesteht die saubere Lösung darin, eine Auflösungstabelle zu erstellen (sagen wir AB). Diese Tabelle würde verknüpfen Amit B: Wenn es ist eine Beziehung , dann würde es einen Datensatz enthalten, wenn es nicht ist , dann wäre es nicht.

Tomalak
quelle
5
Ich habe die akzeptierte Antwort auf diese geändert. Nach den Stimmen zu urteilen, ist diese Antwort für mehr Menschen am klarsten. Ich habe immer noch das Gefühl, dass die Antwort von Tony Andrews die Absicht hinter diesem Design besser erklärt; Probieren Sie es auch aus!
Roman Starkov
2
F: Wann möchten Sie einen NULL FK anstelle einer fehlenden Zeile? A: Nur in einer Version eines zur Optimierung denormalisierten Schemas. In nicht trivialen Schemata können nicht normalisierte Probleme wie diese Probleme verursachen, wenn neue Funktionen erforderlich sind. otoh, das Webdesign-Publikum kümmert sich nicht darum. Ich würde zumindest einen Hinweis zur Vorsicht hinzufügen, anstatt es wie eine gute Designidee klingen zu lassen.
zxq9
3
"Nullfähige Fremdschlüssel sind kein sauberes relationales Datenbankdesign." - Ein nullfreies Datenbankdesign (sechste Normalform) erhöht ausnahmslos die Komplexität. Die erzielten Platzersparnisse werden häufig durch die zusätzliche Programmierarbeit aufgewogen, die zur Realisierung dieser Gewinne erforderlich ist.
Dai
1
Was ist, wenn es sich um eine ABC-Auflösungstabelle handelt? mit optionalem C
Bart Calixto
1
Ich habe versucht zu vermeiden, "weil der Standard es verbietet" zu schreiben, da dies wirklich nichts erklärt.
Tomalak
62

Ein Primärschlüssel definiert einen eindeutigen Bezeichner für jede Zeile in einer Tabelle: Wenn eine Tabelle einen Primärschlüssel hat, haben Sie eine garantierte Möglichkeit, eine beliebige Zeile daraus auszuwählen.

Eine eindeutige Einschränkung identifiziert nicht unbedingt jede Zeile. es gibt nur , dass , wenn ein Zeilenwert in ihren Spalten hat, dann müssen sie eindeutig sein. Dies reicht nicht aus, um jede Zeile eindeutig zu identifizieren , was ein Primärschlüssel tun muss.

Tony Andrews
quelle
10
In SQL Server erlaubt eine eindeutige Einschränkung mit einer nullbaren Spalte den Wert 'null' in dieser Spalte nur einmal (bei identischen Werten für die anderen Spalten der Einschränkung). Eine solche eindeutige Einschränkung verhält sich also im Wesentlichen wie ein pk mit einer nullbaren Spalte.
Gerard
Ich bestätige dasselbe für Oracle (11.2)
Alexander Malakhov
2
In Oracle (ich weiß nichts über SQL Server) kann die Tabelle viele Zeilen enthalten, in denen alle Spalten in einer eindeutigen Einschränkung null sind. Wenn jedoch einige Spalten in der eindeutigen Einschränkung nicht null und einige null sind, wird die Eindeutigkeit erzwungen.
Tony Andrews
Wie trifft dies auf Composite UNIQUE zu?
Dims
1
@Dims Wie bei fast allem anderen in SQL-Datenbanken "hängt es von der Implementierung ab". In den meisten DBs ist ein "Primärschlüssel" tatsächlich eine EINZIGARTIGE Einschränkung darunter. Die Idee des "Primärschlüssels" ist nicht spezieller oder mächtiger als das Konzept von UNIQUE. Der wirkliche Unterschied besteht darin, dass Sie, wenn Sie zwei unabhängige Aspekte einer Tabelle haben, die EINZIGARTIG garantiert werden können, per Definition keine normalisierte Datenbank haben (Sie speichern zwei Datentypen in derselben Tabelle).
zxq9
46

Grundsätzlich ist mit einem NULL in einem mehrspaltigen Primärschlüssel nichts falsch. Aber eine zu haben hat Auswirkungen, die der Designer wahrscheinlich nicht beabsichtigt hat, weshalb viele Systeme einen Fehler auslösen, wenn Sie dies versuchen.

Betrachten Sie den Fall von Modul- / Paketversionen, die als eine Reihe von Feldern gespeichert sind:

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));

Die ersten 5 Elemente des Primärschlüssels sind regelmäßig definierte Teile einer Release-Version, aber einige Pakete haben eine angepasste Erweiterung, die normalerweise keine Ganzzahl ist (wie "rc-foo" oder "vanilla" oder "beta" oder was auch immer jemand anderes für wen vier Felder nicht ausreichen, könnte man sich ausdenken). Wenn ein Paket keine Erweiterung hat, ist es im obigen Modell NULL, und es würde keinen Schaden anrichten, wenn die Dinge so bleiben.

Aber was ist ein NULL? Es soll einen Mangel an Informationen darstellen, ein Unbekannter. Das heißt, vielleicht macht das mehr Sinn:

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));

In dieser Version ist der "ext" -Teil des Tupels NICHT NULL, sondern standardmäßig eine leere Zeichenfolge - die sich semantisch (und praktisch) von einer NULL unterscheidet. Ein NULL ist ein Unbekannter, während ein leerer String eine absichtliche Aufzeichnung von "etwas, das nicht vorhanden ist" ist. Mit anderen Worten, "leer" und "null" sind verschiedene Dinge. Es ist der Unterschied zwischen "Ich habe hier keinen Wert" und "Ich weiß nicht, was der Wert hier ist".

Wenn Sie ein Paket registrieren, dem eine Versionserweiterung fehlt, wissen Sie, dass ihm eine Erweiterung fehlt. Daher ist eine leere Zeichenfolge tatsächlich der richtige Wert. Ein NULL wäre nur dann richtig, wenn Sie nicht wüssten, ob es eine Erweiterung hat oder nicht, oder wenn Sie wüssten, dass dies der Fall ist, aber nicht wissen, was es ist. Diese Situation ist in Systemen, in denen Zeichenfolgenwerte die Norm sind, einfacher zu handhaben, da es keine andere Möglichkeit gibt, eine "leere Ganzzahl" darzustellen, als 0 oder 1 einzufügen, die bei später durchgeführten Vergleichen aufgerollt wird (was der Fall ist) seine eigenen Implikationen) *.

Übrigens sind beide Möglichkeiten in Postgres gültig (da es sich um "Enterprise" -RDMBSs handelt), aber die Vergleichsergebnisse können erheblich variieren, wenn Sie einen NULL in die Mischung werfen - weil NULL == "weiß nicht" also alle Ergebnisse eines Vergleichs mit NULL werden NULL, da Sie nichts Unbekanntes wissen können. ACHTUNG! Überlegen Sie genau , dass: Das bedeutet , dass NULL Vergleichsergebnisse propagieren durch eine Reihe von Vergleichen. Dies kann zu subtilen Fehlern beim Sortieren, Vergleichen usw. führen.

Postgres geht davon aus, dass Sie erwachsen sind und diese Entscheidung selbst treffen können. Oracle und DB2 gehen davon aus, dass Sie nicht bemerkt haben, dass Sie etwas Dummes getan haben, und werfen einen Fehler. Dies ist in der Regel das Richtige, aber nicht immer - Sie könnten eigentlich nicht wissen , und eine NULL in einigen Fällen haben und daher eine Reihe mit einem unbekannten Element verlassen , gegen die sinnvolle Vergleiche sind nicht das richtige Verhalten ist.

In jedem Fall sollten Sie sich bemühen, die Anzahl der NULL-Felder, die Sie im gesamten Schema zulassen, zu eliminieren, und zwar doppelt, wenn es um Felder geht, die Teil eines Primärschlüssels sind. In den allermeisten Fällen ist das Vorhandensein von NULL-Spalten ein Hinweis auf ein nicht normalisiertes (im Gegensatz zu einem absichtlich de-normalisierten) Schemadesign und sollte vor der Annahme sehr gründlich überlegt werden.

[* HINWEIS: Es ist möglich, einen benutzerdefinierten Typ zu erstellen, der die Vereinigung von Ganzzahlen und einen "unteren" Typ darstellt, der semantisch "leer" im Gegensatz zu "unbekannt" bedeutet. Leider führt dies zu einer gewissen Komplexität bei Vergleichsoperationen, und normalerweise lohnt es sich in der Praxis nicht, wirklich typkorrekt zu sein, da Ihnen überhaupt nicht viele NULLWerte erlaubt sein sollten . Das sei gesagt, es wäre schön, wenn RDBMS eine Standard umfassen würde BOTTOMArt neben NULLdie Gewohnheit beiläufig conflating die Semantik von „no value“ mit „unbekanntem Wert“ zu verhindern. ]]

zxq9
quelle
5
Dies ist eine SEHR SCHÖNE Antwort und erklärt viel über NULL-Werte und deren Auswirkungen in vielen Situationen. Sie, Sir, haben jetzt meinen Respekt! Nicht einmal im College habe ich eine so gute Erklärung für NULL-Werte in Datenbanken erhalten. Danke dir!
Ich unterstütze die Hauptidee dieser Antwort. Aber das Schreiben wie "soll einen Mangel an Informationen darstellen, ein Unbekannter", "semantisch (und praktisch) anders als ein NULL", "Ein NULL ist ein Unbekannter", "eine leere Zeichenfolge ist eine absichtliche Aufzeichnung von" etwas, das nicht vorhanden ist "',' NULL ==" Weiß nicht "'usw. sind vage und irreführend und eigentlich nur Mnemoniken für fehlende Aussagen darüber, wie NULL oder ein Wert verwendet wird oder werden kann oder sollte - gemäß dem Rest des Beitrags . (Einschließlich der Inspiration für das (schlechte) Design von SQL NULL-Funktionen.) Sie rechtfertigen oder erklären nichts. Sie sollten erklärt und entlarvt werden.
philipxy
21

NULL == NULL -> false (zumindest in DBMS)

Sie könnten also auch mit zusätzlichen Spalten mit realen Werten keine Beziehungen mit einem NULL-Wert abrufen.

Cogsy
quelle
1
Das klingt nach der besten Antwort, aber ich verstehe immer noch nicht, warum dies bei der Erstellung von Primärschlüsseln verboten ist. Wenn dies nur ein Abrufproblem war, können Sie es where pk_1 = 'a' and pk_2 = 'b'mit normalen Werten verwenden und zu wechseln, where pk_1 is null and pk_2 = 'b'wenn Nullen vorhanden sind.
EoghanM
Oder noch zuverlässiger, 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))/
Jordan Rieger
8
Falsche Antwort. NULL == NULL -> UNBEKANNT. Nicht falsch. Der Haken ist, dass eine Einschränkung nicht als verletzt angesehen wird, wenn das Testergebnis UNBEKANNT ist. Dies macht es oft so, als ob der Vergleich falsch ist, aber das tut es wirklich nicht.
Erwin Smout
4

Die Antwort von Tony Andrews ist anständig. Die eigentliche Antwort ist jedoch, dass dies eine Konvention ist, die von der relationalen Datenbankgemeinschaft verwendet wird und KEINE Notwendigkeit ist. Vielleicht ist es eine gute Konvention, vielleicht auch nicht.

Wenn Sie etwas mit NULL vergleichen, erhalten Sie UNBEKANNT (3. Wahrheitswert). Wie bei Nullen vorgeschlagen wurde, geht jede traditionelle Weisheit in Bezug auf Gleichheit aus dem Fenster. So scheint es auf den ersten Blick.

Aber ich denke nicht, dass dies unbedingt so ist, und selbst SQL-Datenbanken glauben nicht, dass NULL alle Vergleichsmöglichkeiten zerstört.

Führen Sie in Ihrer Datenbank die Abfrage SELECT * FROM VALUES (NULL) aus. UNION SELECT * FROM VALUES (NULL)

Was Sie sehen, ist nur ein Tupel mit einem Attribut, das den Wert NULL hat. Die Union hat hier also die beiden NULL-Werte als gleich erkannt.

Beim Vergleich eines zusammengesetzten Schlüssels mit 3 Komponenten mit einem Tupel mit 3 Attributen (1, 3, NULL) = (1, 3, NULL) <=> 1 = 1 UND 3 = 3 UND NULL = NULL Das Ergebnis ist UNBEKANNT .

Wir könnten aber eine neue Art von Vergleichsoperator definieren, z. ==. X == Y <=> X = Y ODER (X IST NULL UND Y IST NULL)

Ein solcher Gleichheitsoperator würde zusammengesetzte Schlüssel mit Nullkomponenten oder nicht zusammengesetzte Schlüssel mit Nullwerten unproblematisch machen.

Rami Ojares
quelle
1
Nein, die UNION hat die beiden NULL als nicht verschieden anerkannt. Welches ist nicht das gleiche wie "gleich". Versuchen Sie stattdessen UNION ALL und Sie erhalten zwei Zeilen. Und was den "neuen Vergleichsoperator" betrifft, hat SQL ihn bereits. IST NICHT UNTERSCHIEDLICH VON. Das allein reicht aber nicht aus. Die Verwendung in SQL-Konstrukten wie NATURAL JOIN oder der REFERENCES-Klausel eines Fremdschlüssels erfordert noch zusätzliche Optionen für diese Konstrukte.
Erwin Smout
Aha, Erwin Smout. Es ist wirklich eine Freude, Sie auch in diesem Forum zu treffen! Mir war nicht bekannt, dass SQL "IS NOT DISTINCT FROM" ist. Sehr interessant! Aber es scheint genau das zu sein, was ich mit meinem erfundenen == Operator gemeint habe. Können Sie mir erklären, warum Sie das sagen: "Das allein reicht nicht aus"?
Rami Ojares
Die REFERENCES-Klausel baut per Definition auf Gleichheit auf. Eine Art von REFERENZEN, die ein untergeordnetes Tupel / eine untergeordnete Zeile mit einem übergeordneten Tupel / einer übergeordneten Zeile abgleichen, basierend auf den entsprechenden Attributwerten NOT DISTINCT anstelle von (dem strengeren) EQUAL, würde die Fähigkeit erfordern, diese Option anzugeben, die Syntax jedoch nicht erlaube es. Das Gleiche gilt für NATURAL JOIN.
Erwin Smout
Damit ein Fremdschlüssel funktioniert, muss der referenzierte Schlüssel eindeutig sein (dh alle Werte müssen unterschiedlich sein). Dies bedeutet, dass es einen einzelnen Nullwert haben könnte. Alle Nullwerte könnten sich dann auf diese einzelne Null beziehen, wenn die REFERENCES mit dem Operator NOT DISTINCT definiert würden. Ich denke, das wäre besser (im Sinne von nützlicher). Bei JOINs (sowohl äußerlich als auch innerlich) denke ich, dass die strikte Gleichheit besser ist, da sich die "NULL MATCHES" multiplizieren würden, wenn Nullen auf der linken Seite mit allen Nullen auf der rechten Seite übereinstimmen würden.
Rami Ojares
1

Ich glaube immer noch, dass dies ein grundlegender / funktionaler Fehler ist, der durch eine technische Tatsache verursacht wird. Wenn Sie ein optionales Feld haben, anhand dessen Sie einen Kunden identifizieren können, müssen Sie jetzt einen Dummy-Wert in diesen hacken, nur weil NULL! = NULL, nicht besonders elegant, aber ein "Industriestandard".

Adriaan Davel
quelle