La structure appropriée pour ce scénario est un modèle de sous-classe / héritage, et est presque identique au concept que j'ai proposé dans cette réponse: Liste de valeur ordonnée hétérogène .
Le modèle proposé dans cette question est en fait assez proche en ce que l' Animalentité contient le type (ie race) et les propriétés qui sont communes à tous les types. Cependant, deux modifications mineures sont nécessaires:
Supprimez les champs Cat_ID et Dog_ID de leurs entités respectives:
Le concept clé ici est que tout est un Animalpeu importe, race: Cat, Dog, Elephantet ainsi de suite. Étant donné que le point de départ, tout particulièrement racede Animalne pas vraiment besoin d' un identifiant distinct depuis:
- le
Animal_IDest unique
- les
Cat, Doget toutes autres raceentités ajoutées à l'avenir ne représentent pas, en elles-mêmes, un élément particulier Animal; ils ont de sens que lorsqu'il est utilisé en combinaison avec les informations contenues dans l'entité mère, Animal.
Par conséquent, la Animal_IDpropriété dans le Cat, Dog, etc entités est à la fois le PK et le dos FK à l' Animalentité.
Différencier les types de breed:
Le fait que deux propriétés partagent le même nom ne signifie pas nécessairement que ces propriétés sont identiques, même si le nom étant le même implique une telle relation. Dans ce cas, ce que vous avez vraiment est réellement CatBreedet en DogBreedtant que "types" séparés
Notes initiales
- Le SQL est spécifique à Microsoft SQL Server (c'est-à-dire T-SQL). Cela signifie que vous devez faire attention aux types de données car ils ne sont pas identiques sur tous les SGBDR. Par exemple, j'utilise
VARCHARmais si vous avez besoin de stocker quoi que ce soit en dehors de l'ensemble ASCII standard, vous devriez vraiment l'utiliser NVARCHAR.
- Les champs d'identification des tables « de type » (
Race, CatBreedet DogBreed) sont pas auto-incrémentée (c. -à- IDENTITÉ en termes de T-SQL) , car ils sont des constantes d'application (ils font partie de l'application) qui sont des valeurs de recherche statiques dans la et sont représentés sous forme de enums en C # (ou dans d'autres langages). Si des valeurs sont ajoutées, elles sont ajoutées dans des situations contrôlées. Je réserve l'utilisation de champs d'incrémentation automatique pour les données utilisateur qui arrivent via l'application.
- La convention de dénomination que j'utilise consiste à nommer chaque table de sous-classe en commençant par le nom de la classe principale suivi du nom de la sous-classe. Cela permet d'organiser les tables et indique clairement (sans regarder les FK) la relation entre la table de sous-classe et la table d'entité principale.
- Veuillez consulter la section "Édition finale" à la fin pour une note concernant les vues.
"Race" comme "Race" - Approche spécifique

Ce premier ensemble de tables sont les tables de recherche / types:
CREATE TABLE Race
(
RaceID INT NOT NULL PRIMARY KEY
RaceName VARCHAR(50) NOT NULL
);
CREATE TABLE CatBreed
(
CatBreedID INT NOT NULL PRIMARY KEY,
BreedName VARCHAR(50),
CatBreedAttribute1 INT,
CatBreedAttribute2 VARCHAR(10)
-- other "CatBreed"-specific properties as needed
);
CREATE TABLE DogBreed
(
DogBreedID INT NOT NULL PRIMARY KEY,
BreedName VARCHAR(50),
DogBreedAttribute1 TINYINT
-- other "DogBreed"-specific properties as needed
);
Cette deuxième inscription est la principale entité «animale»:
CREATE TABLE Animal
(
AnimalID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
RaceID INT NOT NULL, -- FK to Race
Name VARCHAR(50)
-- other "Animal" properties that are shared across "Race" types
);
ALTER TABLE Animal
ADD CONSTRAINT [FK_Animal_Race]
FOREIGN KEY (RaceID)
REFERENCES Race (RaceID);
Cette troisième série de tableaux sont les entités sous-classe complémentaires qui complètent la définition de chacun Racede Animal:
CREATE TABLE AnimalCat
(
AnimalID INT NOT NULL PRIMARY KEY, -- FK to Animal
CatBreedID INT NOT NULL, -- FK to CatBreed
HairColor VARCHAR(50) NOT NULL
-- other "Cat"-specific properties as needed
);
ALTER TABLE AnimalCat
ADD CONSTRAINT [FK_AnimalCat_CatBreed]
FOREIGN KEY (CatBreedID)
REFERENCES CatBreed (CatBreedID);
ALTER TABLE AnimalCat
ADD CONSTRAINT [FK_AnimalCat_Animal]
FOREIGN KEY (AnimalID)
REFERENCES Animal (AnimalID);
CREATE TABLE AnimalDog
(
AnimalID INT NOT NULL PRIMARY KEY, -- FK to Animal
DogBreedID INT NOT NULL, -- FK to DogBreed
HairColor VARCHAR(50) NOT NULL
-- other "Dog"-specific properties as needed
);
ALTER TABLE AnimalDog
ADD CONSTRAINT [FK_AnimalDog_DogBreed]
FOREIGN KEY (DogBreedID)
REFERENCES DogBreed (DogBreedID);
ALTER TABLE AnimalDog
ADD CONSTRAINT [FK_AnimalDog_Animal]
FOREIGN KEY (AnimalID)
REFERENCES Animal (AnimalID);
Le modèle utilisant un breedtype partagé est affiché après la section "Notes supplémentaires".
Notes complémentaires
- Le concept de
breedsemble être un foyer de confusion. Il a été suggéré par jcolebrand (dans un commentaire sur la question) qui breedest une propriété partagée entre les différents races, et les deux autres réponses l'ont intégré comme tel dans leurs modèles. Il s'agit toutefois d'une erreur, car les valeurs de breedne sont pas partagées entre les différentes valeurs de race. Oui, je suis conscient que les deux autres modèles proposés tentent de résoudre ce problème en faisant raceun parent de breed. Bien que cela résout techniquement le problème des relations, cela n'aide pas à résoudre la question globale de la modélisation de ce qu'il faut faire des propriétés non communes, ni comment gérer un racequi n'en a pas breed. Mais, dans le cas où une telle propriété était garantie d'exister dans tous lesAnimals, je vais également inclure une option pour cela (ci-dessous).
- Les modèles proposés par vijayp et DavidN (qui semblent identiques) ne fonctionnent pas car:
- Ils non plus
- ne pas autoriser le stockage de propriétés non communes (du moins pas pour des instances individuelles
Animal), ou
- exiger que toutes les propriétés de tous les
races soient stockées dans l' Animalentité, ce qui est une façon très plate (et presque non relationnelle) de représenter ces données. Oui, les gens le font tout le temps, mais cela signifie avoir de nombreux champs NULL par ligne pour les propriétés qui ne sont pas destinées à ce particulier raceET savoir quels champs par ligne sont associés au particulier racede cet enregistrement.
- Ils ne permettent pas d'ajouter un
racede Animaldans le futur qui n'a pas breedde propriété. Et même si TOUS Animalont un breed, cela ne changerait pas la structure en raison de ce qui a été noté précédemment breed: cela breeddépend du race(c'est- breedà- dire pour Catn'est pas la même chose que breedpour Dog).
La «race» comme approche de propriété commune / partagée

Notez s'il vous plaît:
Le SQL ci-dessous peut être exécuté dans la même base de données que le modèle présenté ci-dessus:
- Le
Racetableau est le même
- La
Breedtable est neuve
- Les trois
Animaltableaux sont accompagnés d'un2
- Même s'il s'agit d'une
Breedpropriété désormais commune, il ne semble pas juste de ne pas l'avoir Racenoté dans l'entité principale / parent (même si elle est techniquement correcte sur le plan relationnel). Ainsi, les deux RaceIDet BreedIDsont représentés dans Animal2. Afin d'éviter une incompatibilité entre le RaceIDnoté dans Animal2et un BreedIDpour un autre RaceID, j'ai ajouté un FK sur les deux RaceID, BreedIDqui fait référence à une CONTRAINTE UNIQUE de ces champs dans la Breedtable. Je méprise généralement le fait de pointer un FK vers une CONTRAINTE UNIQUE, mais voici l'une des rares raisons valables de le faire. UNE CONTRAINTE UNIQUE est logiquement une «Clé alternative», ce qui la rend valable pour cette utilisation. Veuillez également noter que la Breedtable a toujours un PK juste BreedID.
- La raison de ne pas aller avec juste un PK sur les champs combinés et sans CONTRAINTE UNIQUE est que cela permettrait de
BreedIDrépéter le même sur différentes valeurs de RaceID.
- La raison de ne pas commuter autour de laquelle PK et UNIQUE CONSTRAINT est que cela pourrait ne pas être la seule utilisation de
BreedID, il devrait donc être possible de référencer une valeur spécifique de Breedsans avoir la RaceIDdisponibilité.
- Bien que le modèle suivant fonctionne, il présente deux failles potentielles concernant le concept de partage
Breed(et c'est pourquoi je préfère les tableaux Racespécifiques Breed).
- Il y a une supposition implicite que TOUTES les valeurs de
Breedont les mêmes propriétés. Il n'y a pas de moyen facile dans ce modèle d'avoir des propriétés disparates entre les Dog"races" et les Elephant"races". Cependant, il existe toujours un moyen de le faire, ce qui est noté dans la section "Final Edit".
- Il n'y a aucun moyen de partager une
Breedcourse sur plusieurs courses. Je ne sais pas si c'est souhaitable de le faire (ou peut-être pas dans le concept d'animaux mais peut-être dans d'autres situations qui utiliseraient ce type de modèle), mais ce n'est pas possible ici.
CREATE TABLE Race
(
RaceID INT NOT NULL PRIMARY KEY,
RaceName VARCHAR(50) NOT NULL
);
CREATE TABLE Breed
(
BreedID INT NOT NULL PRIMARY KEY,
RaceID INT NOT NULL, -- FK to Race
BreedName VARCHAR(50)
);
ALTER TABLE Breed
ADD CONSTRAINT [UQ_Breed]
UNIQUE (RaceID, BreedID);
ALTER TABLE Breed
ADD CONSTRAINT [FK_Breed_Race]
FOREIGN KEY (RaceID)
REFERENCES Race (RaceID);
CREATE TABLE Animal2
(
AnimalID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
RaceID INT NOT NULL, -- FK to Race, FK to Breed
BreedID INT NOT NULL, -- FK to Breed
Name VARCHAR(50)
-- other properties common to all "Animal" types
);
ALTER TABLE Animal2
ADD CONSTRAINT [FK_Animal2_Race]
FOREIGN KEY (RaceID)
REFERENCES Race (RaceID);
-- This FK points to the UNIQUE CONSTRAINT on Breed, _not_ to the PK!
ALTER TABLE Animal2
ADD CONSTRAINT [FK_Animal2_Breed]
FOREIGN KEY (RaceID, BreedID)
REFERENCES Breed (RaceID, BreedID);
CREATE TABLE AnimalCat2
(
AnimalID INT NOT NULL PRIMARY KEY, -- FK to Animal
HairColor VARCHAR(50) NOT NULL
);
ALTER TABLE AnimalCat2
ADD CONSTRAINT [FK_AnimalCat2_Animal2]
FOREIGN KEY (AnimalID)
REFERENCES Animal2 (AnimalID);
CREATE TABLE AnimalDog2
(
AnimalID INT NOT NULL PRIMARY KEY,
HairColor VARCHAR(50) NOT NULL
);
ALTER TABLE AnimalDog2
ADD CONSTRAINT [FK_AnimalDog2_Animal2]
FOREIGN KEY (AnimalID)
REFERENCES Animal2 (AnimalID);
Édition finale (j'espère ;-)
- En ce qui concerne la possibilité (et ensuite la difficulté) de gérer des propriétés disparates entre les types de
Breed, il est possible d'employer le même concept de sous-classe / héritage mais avec Breedcomme entité principale. Dans cette configuration, le Breedtableau aurait les propriétés communes à tous les types de Breed(tout comme le Animaltableau) et RaceIDreprésenterait le type de Breed(comme il le fait dans le Animaltableau). Ensuite , vous avez des tables sous - classe tels que BreedCat, BreedDoget ainsi de suite. Pour les petits projets, cela pourrait être considéré comme une «ingénierie excessive», mais il est mentionné comme une option pour les situations qui en bénéficieraient.
Pour les deux approches, il est parfois utile de créer des vues sous forme de raccourcis vers les entités complètes. Par exemple, considérez:
CREATE VIEW Cats AS
SELECT an.AnimalID,
an.RaceID,
an.Name,
-- other "Animal" properties that are shared across "Race" types
cat.CatBreedID,
cat.HairColor
-- other "Cat"-specific properties as needed
FROM Animal an
INNER JOIN AnimalCat cat
ON cat.AnimalID = an.AnimalID
-- maybe add in JOIN(s) and field(s) for "Race" and/or "Breed"
- Bien qu'il ne fasse pas partie des entités logiques, il est assez courant d'avoir des champs d'audit dans les tables pour au moins avoir une idée du moment où les enregistrements sont insérés et mis à jour. Donc en termes pratiques:
- Un
CreatedDatechamp serait ajouté à la Animaltable. Ce champ n'est nécessaire dans aucune des tables de sous-classe (par exemple AnimalCat) car les lignes insérées pour les deux tables doivent être effectuées en même temps dans une transaction.
- Un
LastModifiedDatechamp serait ajouté à la Animaltable et à toutes les tables de sous-classe. Ce champ n'est mis à jour que si cette table particulière est mise à jour: si une mise à jour se produit dans AnimalCatmais pas dans Animalpour un particulier AnimalID, alors seul le LastModifiedDatechamp dans AnimalCatsera défini.