Können Tabellenspalten mit einem Fremdschlüssel NULL sein?

235

Ich habe eine Tabelle, die mehrere ID-Spalten zu anderen Tabellen hat.

Ich möchte, dass ein Fremdschlüssel die Integrität nur erzwingt , wenn ich dort Daten eingebe. Wenn ich zu einem späteren Zeitpunkt ein Update durchführe, um diese Spalte zu füllen, sollte auch die Einschränkung überprüft werden.

(Dies ist wahrscheinlich datenbankserverabhängig. Ich verwende den Tabellentyp MySQL & InnoDB.)

Ich glaube, das ist eine vernünftige Erwartung, aber korrigiere mich, wenn ich falsch liege.

Bender
quelle
6
Ich weiß nichts über MySQL, aber mit MS SQL Server können Fremdschlüssel mit der gewünschten Semantik auf Null gesetzt werden. Ich gehe davon aus, dass dies Standardverhalten ist.
Jeffrey L Whitledge
1
Der Fremdschlüssel kann in mySQL nicht standardmäßig null sein. Der Grund ist einfach. Wenn Sie auf etwas verweisen und es null lassen, verlieren Sie die Datenintegrität. Wenn Sie den Tabellensatz erstellen, lassen Sie null auf NICHT zu und wenden Sie dann die Fremdschlüsseleinschränkung an. Sie können beim Update nicht null setzen, es sollte Ihnen einen Fehler senden, aber Sie können (Sie müssen) diese Spalte einfach nicht aktualisieren und nur die Felder aktualisieren, die Sie ändern müssen.
JoelBonetR

Antworten:

245

Ja, Sie können die Einschränkung nur erzwingen, wenn der Wert nicht NULL ist. Dies kann leicht anhand des folgenden Beispiels getestet werden:

CREATE DATABASE t;
USE t;

CREATE TABLE parent (id INT NOT NULL,
                     PRIMARY KEY (id)
) ENGINE=INNODB;

CREATE TABLE child (id INT NULL, 
                    parent_id INT NULL,
                    FOREIGN KEY (parent_id) REFERENCES parent(id)
) ENGINE=INNODB;


INSERT INTO child (id, parent_id) VALUES (1, NULL);
-- Query OK, 1 row affected (0.01 sec)


INSERT INTO child (id, parent_id) VALUES (2, 1);

-- ERROR 1452 (23000): Cannot add or update a child row: a foreign key 
-- constraint fails (`t/child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY
-- (`parent_id`) REFERENCES `parent` (`id`))

Die erste Einfügung wird übergeben, da wir eine NULL in die einfügen parent_id. Die zweite Einfügung schlägt aufgrund der Fremdschlüsseleinschränkung fehl, da wir versucht haben, einen Wert einzufügen, der in der parentTabelle nicht vorhanden ist .

Daniel Vassallo
quelle
16
Die übergeordnete Tabelle kann auch mit der ID INT NOT NULL deklariert werden.
Wird
@CJDennis Wenn Sie festlegen, dass nur eine Zeile eine Null-ID haben kann, kann sie als Fallback-Wert für andere Zeilen verwendet werden. (Obwohl es für die Datenbank möglicherweise besser funktioniert, wenn Sie nur mehr Spalten verwenden.) Die Standardeinschränkung scheint ein Problem zu sein, wenn Sie später wissen möchten, ob ein Wert ursprünglich als "Standard" (mit null) oder auf a festgelegt wurde Wert, der zufällig mit "Standard" identisch ist. Wenn Sie eine Zeile mit einer Null-ID haben, können Sie klar angeben, dass diese Zeile nicht als normale Zeile verwendet werden soll, und die Zeile als Möglichkeit verwenden, eine Art dynamischen Standardwert für andere Zeilen bereitzustellen.
Ouroborus
1
Ich denke, der parent_id INT NULLTeil ist (wörtlich) gleichparent_id int default null
Randnotiz für Java-Benutzer: Wenn Sie ibatis oder ein anderes ORM verwenden und das Grundelement intanstelle IntegerIhrer Klassenmitglieder verwenden, ist der Standardwert niemals null, sondern 0, und Sie werden die Einschränkung nicht erfüllen.
Jim Ford
32

Ich habe festgestellt, dass beim Einfügen die Nullspaltenwerte speziell als NULL deklariert werden müssen, da sonst ein Fehler bei der Einschränkung von Einschränkungen angezeigt wird (im Gegensatz zu einer leeren Zeichenfolge).

Backslider
quelle
8
Könnten Sie nicht den Standardwert NULL für die Spalte festlegen, um dies zuzulassen?
Kevin Coulombe
Ja, in den meisten Sprachen unterscheidet sich NULL von einer leeren Zeichenfolge. Vielleicht subtil am Anfang, aber wichtig, um sich zu erinnern.
Gary
Hey Backslider, Sie sagen "(im Gegensatz zu einer leeren Zeichenfolge)", aber ich glaube nicht, dass Sie gemeint haben, dass Sie einen Wert für eine leere Zeichenfolge einfügen würden, sondern dass Sie keinen Wert für den Wert bei angeben alles? dh Sie erwähnen nicht einmal die Spalte in Ihrer INSERT INTO {table} {list_of_columns}? Weil das für mich gilt; Das Auslassen der Erwähnung der Spalte führt zu einem Fehler, aber das Einschließen und explizite Setzen auf NULL behebt den Fehler. Wenn ich richtig liege, denke ich, dass @ Garys Kommentar nicht zutrifft (weil Sie keine leere Zeichenfolge gemeint haben), aber @ Kevin Coulombes könnte hilfreich sein ...
The Red Pea
Ja, @ KevinCoulombes Vorschlag funktioniert. Ich habe hier beschrieben, wie dies mit den Migrationsskripten von Entity Framework Core erreicht werden kann
The Red Pea
Es ist wichtig darauf hinzuweisen, dass die Begründung für die explizite Aktualisierung eines Datensatzes mit NULL-Fremdschlüsseln nur für Zeichenfolgentypen (varchar usw.) gilt, da andernfalls eine leere Zeichenfolge als Standard übergeben werden kann. Dies ist bei MySQL der Fall und führt beim Update zu einem Integritätsfehler.
CodeMantle
4

Ja, das wird so funktionieren, wie Sie es erwarten. Leider scheint es mir schwer zu fallen, eine explizite Aussage dazu im MySQL-Handbuch zu finden .

Fremdschlüssel bedeuten, dass der Wert in der anderen Tabelle vorhanden sein muss. NULL bezieht sich auf das Fehlen eines Werts. Wenn Sie also eine Spalte auf NULL setzen, ist es nicht sinnvoll, zu versuchen, diesbezügliche Einschränkungen durchzusetzen.

davidtbernal
quelle
Der Fremdschlüssel muss standardmäßig auf einen Schlüssel (Primärschlüssel) verweisen, der nicht NULL ist. In der Entwicklungsphase müssen jedoch mehrere Daten zuerst in die untergeordnete Tabelle eingefügt werden, von der wir nicht wissen, auf wen er sich bezieht (die übergeordnete Tabelle). . Deshalb ist der Wert NULL zulässig. In der Produktion wird NULL ein Entwurfsablauf sein, der grob gesagt werden kann.
Vimal Krishna
2

Das obige funktioniert, aber das funktioniert nicht. Beachten Sie die KASKADE EIN LÖSCHEN

CREATE DATABASE t;
USE t;

CREATE TABLE parent (id INT NOT NULL,
                 PRIMARY KEY (id)
) ENGINE=INNODB;

CREATE TABLE child (id INT NULL, 
                parent_id INT NULL,
                FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE

) ENGINE=INNODB;


INSERT INTO child (id, parent_id) VALUES (1, NULL);
-- Query OK, 1 row affected (0.01 sec)
MrFabulous
quelle
4
Was meinst du mit "oben"? Beachten Sie, dass sich die Reihenfolge ändern kann, wenn Sie sich auf eine andere Antwort beziehen.
d219
2

Ja, der Wert kann NULL sein, aber Sie müssen explizit sein. Ich habe die gleiche Situation schon einmal erlebt, und es ist leicht zu vergessen, WARUM dies geschieht. Daher dauert es ein wenig, sich daran zu erinnern, was zu tun ist.

Wenn die übermittelten Daten als leere Zeichenfolge umgewandelt oder interpretiert werden, schlägt dies fehl. Wenn Sie den Wert beim EINFÜGEN oder AKTUALISIEREN jedoch explizit auf NULL setzen, können Sie loslegen.

Aber das ist der Spaß am Programmieren, nicht wahr? Erstellen Sie unsere eigenen Probleme und beheben Sie sie dann! Prost!

Toby Crain
quelle
1

Eine andere Möglichkeit, dies zu umgehen, besteht darin, ein DEFAULT-Element in die andere Tabelle einzufügen. Beispielsweise würde ein Verweis auf uuid = 00000000-0000-0000-0000-000000000000 in der anderen Tabelle keine Aktion anzeigen. Sie müssen auch alle Werte für diese ID auf "neutral" setzen, z. B. 0, leere Zeichenfolge, null, um Ihre Codelogik nicht zu beeinflussen.

Wildhammer
quelle
2
Das ist nicht dasselbe. Ein Standardwert oder "neutraler" Wert ist nicht dasselbe wie NULL, das Fehlen eines Werts. Ohne den Wert eines Standardwerts über NULL zu diskutieren, ist Ihre Phrasierung ein wenig gemischt. "Ein anderer Weg, dies zu umgehen, wäre das Einfügen eines Null-Elements in die andere Tabelle" sollte eher so etwas wie "Ein anderer Weg, dies zu umgehen, wäre das Einfügen eines DEFAULT-Elements in die andere Tabelle"
blindguy
0

Ich habe mich auch an dieses Thema gehalten. Aber ich habe es einfach gelöst, indem ich den Fremdschlüssel als definiert habe unsigned integer. Finden Sie das folgende Beispiel-

CREATE TABLE parent (
   id int(10) UNSIGNED NOT NULL,
    PRIMARY KEY (id)
) ENGINE=INNODB;

CREATE TABLE child (
    id int(10) UNSIGNED NOT NULL,
    parent_id int(10) UNSIGNED DEFAULT NULL,
    FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE
) ENGINE=INNODB;
Shams Reza
quelle