Je veux avoir une relation un-à-plusieurs dans laquelle pour chaque parent, un ou zéro des enfants est marqué comme "favori". Cependant, tous les parents n'auront pas d'enfant. (Considérez les parents comme des questions sur ce site, les enfants comme des réponses et les favoris comme la réponse acceptée.) Par exemple,
TableA
Id INT PRIMARY KEY
TableB
Id INT PRIMARY KEY
Parent INT NOT NULL FOREIGN KEY REFERENCES TableA.Id
D'après ce que je vois, je peux soit ajouter la colonne suivante au tableau A:
FavoriteChild INT NULL FOREIGN KEY REFERENCES TableB.Id
ou la colonne suivante du tableau B:
IsFavorite BIT NOT NULL
Le problème avec la première approche est qu'elle introduit une clé étrangère nullable, qui, je comprends, n'est pas sous une forme normalisée. Le problème avec la deuxième approche est que plus de travail doit être fait pour s'assurer qu'au plus un enfant est le favori.
Quels types de critères dois-je utiliser pour déterminer quelle approche utiliser? Ou y a-t-il d'autres approches que je n'envisage pas?
J'utilise SQL Server 2012.
Une autre façon (sans Nulls et sans cycles dans le FOREIGN KEY
relations) est d'avoir une troisième table pour stocker les "enfants préférés". Dans la plupart des SGBD, vous aurez besoin d'une contrainte UNIQUE
supplémentaire sur TableB
.
@Aaron a été plus rapide à identifier que la convention de dénomination ci-dessus est plutôt lourde et peut entraîner des erreurs. Il est généralement préférable (et vous gardera sain d'esprit) si vous n'avez pas de colonnes Id
partout dans vos tables et si les colonnes (qui sont jointes) ont les mêmes noms dans les nombreuses tables qui apparaissent. Alors, voici un changement de nom:
Parent
ParentID INT NOT NULL PRIMARY KEY
Child
ChildID INT NOT NULL PRIMARY KEY
ParentID INT NOT NULL FOREIGN KEY REFERENCES Parent (ParentID)
UNIQUE (ParentID, ChildID)
FavoriteChild
ParentID INT NOT NULL PRIMARY KEY
ChildID INT NOT NULL
FOREIGN KEY (ParentID, ChildID)
REFERENCES Child (ParentID, ChildID)
Dans SQL-Server (que vous utilisez), vous avez également la possibilité de la colonne IsFavorite
bit que vous mentionnez. L'enfant préféré unique par parent peut être accompli via un index unique filtré:
Parent
ParentID INT NOT NULL PRIMARY KEY
Child
ChildID INT NOT NULL PRIMARY KEY
ParentID INT NOT NULL FOREIGN KEY REFERENCES Parent (ParentID)
IsFavorite BIT NOT NULL
CREATE UNIQUE INDEX is_FavoriteChild
ON Child (ParentID)
WHERE IsFavorite = 1 ;
Et la principale raison pour laquelle votre option 1 n'est pas recommandée, du moins pas dans SQL-Server, est que le modèle de chemins circulaires dans les références de clé étrangère a quelques problèmes.
Lisez un article assez ancien: SQL By Design: The Circular Reference
Lorsque vous insérez ou supprimez des lignes des deux tableaux, vous rencontrerez le problème "poulet et œuf". Quelle table dois-je insérer en premier - sans violer aucune contrainte?
Pour résoudre cela, vous devez définir au moins une colonne nullable. (OK, techniquement, vous n'êtes pas obligé, vous pouvez avoir toutes les colonnes comme NOT NULL
mais uniquement dans les SGBD, comme Postgres et Oracle, qui ont implémenté des contraintes reportables. Voir la réponse de @ Erwin dans une question similaire: Contrainte de clé étrangère complexe dans SQLAlchemy sur la façon dont cela peut être fait dans Postgres). Pourtant, cette configuration ressemble à du patinage sur de la glace mince.
Vérifiez également une question presque identique à SO (mais pour MySQL) En SQL, est-il correct que deux tables se réfèrent l'une à l'autre?) où ma réponse est à peu près la même. MySQL n'a cependant pas d'index partiels, donc les seules options viables sont le FK nullable et la solution de table supplémentaire.
Cela dépend de votre priorité. Voulez-vous éviter le travail ou souhaitez-vous respecter les règles de normalisation les plus strictes?
Personnellement, je pense qu'il vaut mieux avoir IsFavorite
dans la table enfant, et je serais prêt à faire le travail pour m'assurer qu'au plus un enfant pour chaque parent est le favori de ce parent. La raison principale n'a rien à voir avec la clé étrangère nullable: je n'aime pas du tout l'idée de clés étrangères pointant dans les deux sens.
La suggestion de @ ypercube est également un bon compromis.
En passant, s'il vous plaît, s'il vous plaît, veuillez ne pas joncher votre schéma avec des noms de colonnes sans signification comme Id
. Je préférerais de beaucoup que le Id
soit nommé de manière significative tout au long du schéma. Identifie-t-il un auteur? Ok, appelez-le AuthorID
. Représente-t-il un produit? Ok, ProductID
. Est-ce un employé et, dans certains cas, fait référence à un gestionnaire? Ok, EmployeeID
et ManagerID
ont plus de sens pour moi que ID
et Parent
. Bien qu'il puisse sembler logique de laisser cela de côté (et redondant pour le mettre), lorsque vous commencez à écrire des jointures complexes (ou postez des requêtes ici), vous ressentirez certainement des malédictions lorsque vous essayez de désosser un tas de jointures à ce point à des colonnes comme a.Parent = b.ID
... tache.
Les données appartiennent à la table enfant. Nous le maintenons correct avec un déclencheur sur la table pour nous assurer qu'un seul et unique enregistrement est marqué comme favori (ou dans notre cas comme adresse préférée).
Cependant, l'idée de @ ypercube d'une table séparée est également bonne.