Les colonnes de table avec une clé étrangère peuvent-elles être NULL?

235

J'ai une table qui a plusieurs colonnes d'identification vers d'autres tables.

Je veux qu'une clé étrangère ne force l'intégrité que si j'y mets des données. Si je fais une mise à jour ultérieurement pour remplir cette colonne, elle doit également vérifier la contrainte.

(Cela dépend probablement du serveur de base de données, j'utilise le type de table MySQL et InnoDB)

Je crois que c'est une attente raisonnable, mais corrigez-moi si je me trompe.

cintreuse
la source
6
Je ne sais pas pour MySQL, mais MS SQL Server permet aux clés étrangères d'être annulables avec la sémantique que vous souhaitez. Je pense que c'est un comportement standard.
Jeffrey L Whitledge
1
la clé étrangère, ne peut pas être nulle par défaut dans mySQL, la raison est simple, si vous référencez quelque chose et que vous le laissez nul, vous perdrez l'intégrité des données. lorsque vous créez l'ensemble de tables, autorisez null à NOT, puis appliquez la contrainte de clé étrangère. Vous ne pouvez pas définir null lors de la mise à jour, cela devrait vous envoyer une erreur, mais vous pouvez (vous devez) simplement ne pas mettre à jour cette colonne et mettre à jour uniquement les champs que vous devez modifier.
JoelBonetR

Réponses:

245

Oui, vous ne pouvez appliquer la contrainte que lorsque la valeur n'est pas NULL. Cela peut être facilement testé avec l'exemple suivant:

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

Le premier insert passera car nous insérons un NULL dans le parent_id. La deuxième insertion échoue en raison de la contrainte de clé étrangère, car nous avons essayé d'insérer une valeur qui n'existe pas dans la parenttable.

Daniel Vassallo
la source
16
La table parent peut également être déclarée avec l'ID INT NOT NULL.
Will
@CJDennis Si vous faites en sorte qu'une seule ligne puisse avoir un ID nul, elle pourrait être utilisée comme valeurs de secours pour d'autres lignes. (Bien que cela puisse fonctionner mieux pour la base de données si vous utilisez simplement plus de colonnes.) La contrainte par défaut semble être un problème si vous voulez savoir plus tard si une valeur a été initialement définie comme "par défaut" (en utilisant null) ou définie sur un valeur qui se trouve être identique à "par défaut". En ayant une ligne avec un id nul, vous pouvez clairement indiquer que cette ligne ne doit pas être utilisée comme une ligne normale, et pouvez utiliser la ligne comme un moyen de fournir une sorte de valeur dynamique par défaut pour d'autres lignes.
Ouroborus
1
je pense que la parent_id INT NULLpartie est (verbalement) égale àparent_id int default null
Note latérale pour les utilisateurs de Java, si vous utilisez ibatis ou un autre ORM et utilisez la primitive intau lieu de Integerdans vos membres de classe, la valeur par défaut ne sera jamais nulle, mais sera 0 et vous échouerez à la contrainte.
Jim Ford
32

J'ai trouvé que lors de l'insertion, les valeurs de colonne nulles devaient être spécifiquement déclarées comme NULL, sinon j'obtiendrais une erreur de violation de contrainte (par opposition à une chaîne vide).

Récidiviste
la source
8
Pourriez-vous ne pas définir une valeur par défaut NULL sur la colonne pour autoriser cela?
Kevin Coulombe
Oui, dans la plupart des langues, NULL est différent d'une chaîne vide. Peut-être subtil au début, mais critique à retenir.
Gary
Hey Backslider, vous dites "(par opposition à une chaîne vide)", mais je ne pense pas que vous vouliez dire que vous INSÉRERiez une valeur de chaîne vide, mais plutôt que vous ne spécifiez pas de valeur à pour la valeur à tout? c'est-à-dire que vous ne mentionnez même pas la colonne dans votre INSERT INTO {table} {list_of_columns}? Parce que c'est vrai pour moi; L'omission de la mention de la colonne provoque une erreur, mais l'inclusion et la définition explicite de la valeur NULL corrige l'erreur. Si j'ai raison, je pense que le commentaire de @ Gary ne s'applique pas (parce que vous ne vouliez pas dire une chaîne vide), mais @Kevin Coulombe pourrait être utile ...
The Red Pea
Oui, la suggestion de @ KevinCoulombe fonctionne, j'ai décrit comment y parvenir avec les scripts de migration d'Entity Framework Core, ici
The Red Pea
Il est important de souligner que la raison d'être explicite lors de la mise à jour d'un enregistrement contenant des clés étrangères NULL s'applique uniquement aux types de chaîne (varchar, etc.), car sinon une chaîne vide peut être transmise par défaut. C'est le cas avec MySQL et entraîne une erreur d'intégrité lors de la mise à jour.
CodeMantle
4

Oui, cela fonctionnera comme vous vous y attendez. Malheureusement, je semble avoir du mal à trouver une déclaration explicite à ce sujet dans le manuel MySQL .

Les clés étrangères signifient que la valeur doit exister dans l'autre table. NULL fait référence à l'absence de valeur, donc lorsque vous définissez une colonne sur NULL, cela n'aurait aucun sens d'essayer d'imposer des contraintes sur cela.

davidtbernal
la source
Par conception, la clé étrangère doit faire référence à une clé (principale) qui n'est pas NULL, mais pendant la phase de développement lorsque nous devons d'abord insérer plusieurs données dans la table enfant, que nous ne savons pas à qui elle se référera (la table parent) . C'est pourquoi nous avons la valeur NULL autorisée. En production, avoir NULL sera un flux de conception, ce qui peut être dit en gros.
vimal krishna
2

Ce qui précède fonctionne mais cela ne fonctionne pas. Notez le ON DELETE CASCADE

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
la source
4
Qu'entendez-vous par «ce qui précède»? Notez que si vous faites référence à une autre réponse, la commande peut changer.
d219
2

Oui, la valeur peut être NULL, mais vous devez être explicite. J'ai déjà vécu cette même situation auparavant, et il est facile d'oublier POURQUOI cela se produit, et il faut donc un peu de temps pour se rappeler ce qui doit être fait.

Si les données soumises sont converties ou interprétées comme une chaîne vide, cela échouera. Cependant, en définissant explicitement la valeur sur NULL lors de l'insertion ou de la mise à jour, vous êtes prêt à partir.

Mais c'est le plaisir de la programmation, n'est-ce pas? Créer nos propres problèmes et les résoudre! À votre santé!

Toby Crain
la source
1

Une autre solution consiste à insérer un élément DEFAULT dans l'autre table. Par exemple, toute référence à uuid = 00000000-0000-0000-0000-000000000000 sur l'autre table n'indiquerait aucune action. Vous devez également définir toutes les valeurs pour que cet identifiant soit "neutre", par exemple 0, chaîne vide, null afin de ne pas affecter votre logique de code.

Marteau sauvage
la source
2
Ce n'est pas la même chose. Une valeur par défaut ou "neutre" n'est pas la même chose que NULL, l'absence de valeur. Sans discuter du mérite d'une valeur par défaut sur un NULL, votre phrasé est un peu mélangé. "Un autre moyen de contourner cela serait d'insérer un élément nul dans l'autre table" devrait dire quelque chose de plus comme "Un autre moyen de contourner ce
problème
0

Je suis également resté sur cette question. Mais j'ai résolu simplement en définissant la clé étrangère comme unsigned integer. Trouvez l'exemple ci-dessous-

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
la source