La struttura corretta per questo scenario è un modello di Sottoclasse / Ereditarietà ed è quasi identica al concetto che ho proposto in questa risposta: Elenco di valori ordinato eterogeneo .
Il modello proposto in questa domanda è in realtà abbastanza vicino in quanto l' Animal
entità contiene il tipo (cioè race
) e le proprietà che sono comuni a tutti i tipi. Tuttavia, sono necessarie due modifiche minori:
Rimuovere i campi Cat_ID e Dog_ID dalle rispettive entità:
Il concetto chiave qui è che tutto è Animal
, indipendentemente da race
: Cat
, Dog
, Elephant
, e così via. Dato questo punto di partenza, ogni particolare race
di Animal
non ha davvero bisogno di un identificatore separato poiché:
- il
Animal_ID
è unico
- il
Cat
, Dog
e tutte le race
entità aggiuntive aggiunte in futuro, da sole, non rappresentano pienamente alcun particolare Animal
; hanno solo senso se usato in combinazione con le informazioni contenute nel controllante, Animal
.
Quindi, la Animal_ID
proprietà nel Cat
, Dog
ecc entità è sia il PK e la parte posteriore FK alla Animal
entità.
Distingue tra tipi di breed
:
Solo perché due proprietà condividono lo stesso nome non significa necessariamente che tali proprietà siano uguali, anche se il nome è lo stesso implica una relazione del genere. In questo caso, quello che hai veramente è in realtà CatBreed
e DogBreed
come "tipi" separati
Note iniziali
- L'SQL è specifico di Microsoft SQL Server (ovvero è T-SQL). Significato, fare attenzione ai tipi di dati in quanto non sono gli stessi in tutti i RDBMS. Ad esempio, sto usando,
VARCHAR
ma se hai bisogno di archiviare qualcosa al di fuori del set ASCII standard, dovresti davvero usarlo NVARCHAR
.
- I campi ID delle tabelle di "tipo" (
Race
, CatBreed
e DogBreed
) sono non incremento automatico (cioè IDENTITÀ in termini di T-SQL) perché sono costanti di applicazione (cioè sono parte dell'applicazione) che sono valori di ricerca statiche nel database e sono rappresentati come enum
in C # (o altre lingue). Se vengono aggiunti valori, vengono aggiunti in situazioni controllate. Riservo l'uso dei campi di incremento automatico per i dati dell'utente che arrivano tramite l'applicazione.
- La convenzione di denominazione che uso è quella di nominare ogni tabella di sottoclasse iniziando con il nome della classe principale seguito dal nome della sottoclasse. Questo aiuta a organizzare le tabelle e indica chiaramente (senza guardare agli FK) la relazione tra la tabella delle sottoclassi e la tabella delle entità principali.
- Si prega di consultare la sezione "Modifica finale" alla fine per una nota relativa a Views.
"Razza" come "Gara" - Approccio specifico
Questo primo set di tabelle sono le tabelle di ricerca / tipi:
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
);
Questo secondo elenco è l'entità "Animale" principale:
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);
Questa terza serie di tabelle sono le entità della sottoclasse gratuite che completano la definizione di ciascuna Race
di 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);
Il modello che utilizza un breed
tipo condiviso viene visualizzato dopo la sezione "Note aggiuntive".
Note aggiuntive
- Il concetto di
breed
sembra essere un punto focale per la confusione. È stato suggerito da jcolebrand (in un commento sulla domanda) che breed
è una proprietà condivisa tra le diverse race
s, e le altre due risposte l'hanno integrata come tale nei loro modelli. Questo è un errore, tuttavia, perché i valori di breed
non sono condivisi tra i diversi valori di race
. Sì, sono consapevole che gli altri due modelli proposti tentano di risolvere questo problema creando race
un genitore breed
. Sebbene ciò risolva tecnicamente il problema delle relazioni, non aiuta a risolvere la domanda generale di modellazione su cosa fare delle proprietà non comuni, né su come gestire un race
che non ha un breed
. Ma, nel caso in cui una tale proprietà fosse garantita per tuttiAnimal
s, includerò anche un'opzione per quello (sotto).
- I modelli proposti da vijayp e DavidN (che sembrano identici) non funzionano perché:
- Neanche loro
- non consentire la memorizzazione di proprietà non comuni (almeno non per singole istanze di nessuno
Animal
), oppure
- richiedono che tutte le proprietà di tutti gli oggetti
race
siano archiviate Animal
nell'entità che è un modo molto piatto (e quasi non relazionale) di rappresentare questi dati. Sì, le persone lo fanno sempre, ma significa avere molti campi NULL per riga per le proprietà che non sono pensate per quel particolare race
E sapere quali campi per riga sono associati al particolare race
di quel record.
- Essi non consentono l'aggiunta di una
race
di Animal
in futuro, che non ha breed
come una proprietà. E anche se TUTTI Animal
hanno un breed
, ciò non cambierebbe la struttura a causa di ciò che è stato precedentemente notato breed
: ciò breed
dipende dal race
(cioè breed
per Cat
non è la stessa cosa di breed
per Dog
).
"Razza" come approccio comune / condiviso
Notare che:
L'SQL seguente può essere eseguito nello stesso database del modello presentato sopra:
- Il
Race
tavolo è lo stesso
- Il
Breed
tavolo è nuovo
- Le tre
Animal
tabelle sono state aggiunte con a2
- Anche se
Breed
è una proprietà ormai comune, non sembra giusto non aver Race
notato nell'entità principale / principale (anche se tecnicamente è relazionalmente corretta). Quindi, entrambi RaceID
e BreedID
sono rappresentati in Animal2
. Al fine di evitare una discrepanza tra la RaceID
nota in Animal2
e una BreedID
che è per un diverso RaceID
, ho aggiunto un FK su entrambi RaceID, BreedID
che fa riferimento a UN VINCOLO UNICO di quei campi nella Breed
tabella. Di solito disprezzo indicare un FK verso un VINCOLO UNICO, ma ecco uno dei pochi motivi validi per farlo. Una VINCITA UNICA è logicamente una "Chiave alternativa", che la rende valida per questo uso. Si noti inoltre che la Breed
tabella ha ancora un PK solo su BreedID
.
- La ragione per non andare con un solo PK sui campi combinati e nessun VINCOLO UNICO è che permetterebbe
BreedID
di ripetere lo stesso su valori diversi di RaceID
.
- La ragione per non cambiare il PK e UNICO VINCITORE è che questo potrebbe non essere il solo utilizzo di
BreedID
, quindi dovrebbe essere ancora possibile fare riferimento a un valore specifico di Breed
senza avere RaceID
disponibile.
- Mentre il seguente modello funziona, ha due potenziali difetti riguardo al concetto di condiviso
Breed
(e sono il motivo per cui preferisco le tabelle Race
specifiche Breed
).
- Si presume implicitamente che TUTTI i valori di
Breed
abbiano le stesse proprietà. In questo modello non esiste un modo semplice per avere proprietà disparate tra Dog
"razze" e Elephant
"razze". Tuttavia, c'è ancora un modo per farlo, che è annotato nella sezione "Modifica finale".
- Non c'è modo di condividere una
Breed
tra più di una gara. Non sono sicuro che sia desiderabile farlo (o forse non nel concetto di animali, ma forse in altre situazioni che utilizzerebbero questo tipo di modello), ma qui non è possibile.
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);
Modifica finale (speriamo ;-)
- Per quanto riguarda la possibilità (e quindi la difficoltà) di gestire proprietà disparate tra tipi di
Breed
, è possibile utilizzare lo stesso concetto di sottoclasse / eredità ma con Breed
l'entità principale. In questa configurazione la Breed
tabella avrebbe le proprietà comuni a tutti i tipi di Breed
(proprio come la Animal
tabella) e RaceID
rappresenterebbe il tipo di Breed
(come nella Animal
tabella). Poi si dovrebbe avere tavoli sottoclassi, come BreedCat
, BreedDog
e così via. Per i progetti più piccoli questo potrebbe essere considerato "ingegneria eccessiva", ma viene menzionato come un'opzione per le situazioni che ne trarrebbero beneficio.
Per entrambi gli approcci, a volte aiuta a creare viste come scorciatoie per le entità complete. Ad esempio, considera:
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"
- Sebbene non facciano parte delle entità logiche, è abbastanza comune avere campi di controllo nelle tabelle per avere almeno un'idea di quando i record vengono inseriti e aggiornati. Quindi in termini pratici:
- Un
CreatedDate
campo verrebbe aggiunto alla Animal
tabella. Questo campo non è necessario in nessuna delle tabelle delle sottoclassi (ad es. AnimalCat
) Poiché le righe che vengono inserite per entrambe le tabelle devono essere eseguite contemporaneamente all'interno di una transazione.
- Un
LastModifiedDate
campo verrebbe aggiunto alla Animal
tabella e a tutte le tabelle delle sottoclassi. Questo campo viene aggiornato solo se quella particolare tabella viene aggiornata: se si verifica un aggiornamento AnimalCat
ma non Animal
per un particolare AnimalID
, verrà impostato solo il LastModifiedDate
campo in AnimalCat
.