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' Animal
entité 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 Animal
peu importe, race
: Cat
, Dog
, Elephant
et ainsi de suite. Étant donné que le point de départ, tout particulièrement race
de Animal
ne pas vraiment besoin d' un identifiant distinct depuis:
- le
Animal_ID
est unique
- les
Cat
, Dog
et toutes autres race
entité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_ID
propriété dans le Cat
, Dog
, etc entités est à la fois le PK et le dos FK à l' Animal
entité.
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 CatBreed
et en DogBreed
tant 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
VARCHAR
mais 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
, CatBreed
et 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 enum
s 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 Race
de 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 breed
type partagé est affiché après la section "Notes supplémentaires".
Notes complémentaires
- Le concept de
breed
semble être un foyer de confusion. Il a été suggéré par jcolebrand (dans un commentaire sur la question) qui breed
est une propriété partagée entre les différents race
s, 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 breed
ne 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 race
un 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 race
qui n'en a pas breed
. Mais, dans le cas où une telle propriété était garantie d'exister dans tous lesAnimal
s, 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
race
s soient stockées dans l' Animal
entité, 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 race
ET savoir quels champs par ligne sont associés au particulier race
de cet enregistrement.
- Ils ne permettent pas d'ajouter un
race
de Animal
dans le futur qui n'a pas breed
de propriété. Et même si TOUS Animal
ont un breed
, cela ne changerait pas la structure en raison de ce qui a été noté précédemment breed
: cela breed
dépend du race
(c'est- breed
à- dire pour Cat
n'est pas la même chose que breed
pour 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
Race
tableau est le même
- La
Breed
table est neuve
- Les trois
Animal
tableaux sont accompagnés d'un2
- Même s'il s'agit d'une
Breed
propriété désormais commune, il ne semble pas juste de ne pas l'avoir Race
noté dans l'entité principale / parent (même si elle est techniquement correcte sur le plan relationnel). Ainsi, les deux RaceID
et BreedID
sont représentés dans Animal2
. Afin d'éviter une incompatibilité entre le RaceID
noté dans Animal2
et un BreedID
pour un autre RaceID
, j'ai ajouté un FK sur les deux RaceID, BreedID
qui fait référence à une CONTRAINTE UNIQUE de ces champs dans la Breed
table. 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 Breed
table 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
BreedID
ré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 Breed
sans avoir la RaceID
disponibilité.
- 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 Race
spécifiques Breed
).
- Il y a une supposition implicite que TOUTES les valeurs de
Breed
ont 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
Breed
course 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 Breed
comme entité principale. Dans cette configuration, le Breed
tableau aurait les propriétés communes à tous les types de Breed
(tout comme le Animal
tableau) et RaceID
représenterait le type de Breed
(comme il le fait dans le Animal
tableau). Ensuite , vous avez des tables sous - classe tels que BreedCat
, BreedDog
et 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
CreatedDate
champ serait ajouté à la Animal
table. 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
LastModifiedDate
champ serait ajouté à la Animal
table 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 AnimalCat
mais pas dans Animal
pour un particulier AnimalID
, alors seul le LastModifiedDate
champ dans AnimalCat
sera défini.