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' Animalentità 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 racedi Animalnon ha davvero bisogno di un identificatore separato poiché:
- il
Animal_IDè unico
- il
Cat, Doge tutte le raceentità 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_IDproprietà nel Cat, Dogecc entità è sia il PK e la parte posteriore FK alla Animalentità.
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à CatBreede DogBreedcome "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,
VARCHARma 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, CatBreede 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 enumin 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 Racedi 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 breedtipo condiviso viene visualizzato dopo la sezione "Note aggiuntive".
Note aggiuntive
- Il concetto di
breedsembra essere un punto focale per la confusione. È stato suggerito da jcolebrand (in un commento sulla domanda) che breedè una proprietà condivisa tra le diverse races, e le altre due risposte l'hanno integrata come tale nei loro modelli. Questo è un errore, tuttavia, perché i valori di breednon sono condivisi tra i diversi valori di race. Sì, sono consapevole che gli altri due modelli proposti tentano di risolvere questo problema creando raceun 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 raceche non ha un breed. Ma, nel caso in cui una tale proprietà fosse garantita per tuttiAnimals, 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
racesiano archiviate Animalnell'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 raceE sapere quali campi per riga sono associati al particolare racedi quel record.
- Essi non consentono l'aggiunta di una
racedi Animalin futuro, che non ha breedcome una proprietà. E anche se TUTTI Animalhanno un breed, ciò non cambierebbe la struttura a causa di ciò che è stato precedentemente notato breed: ciò breeddipende dal race(cioè breedper Catnon è la stessa cosa di breedper Dog).
"Razza" come approccio comune / condiviso

Notare che:
L'SQL seguente può essere eseguito nello stesso database del modello presentato sopra:
- Il
Racetavolo è lo stesso
- Il
Breedtavolo è nuovo
- Le tre
Animaltabelle sono state aggiunte con a2
- Anche se
Breedè una proprietà ormai comune, non sembra giusto non aver Racenotato nell'entità principale / principale (anche se tecnicamente è relazionalmente corretta). Quindi, entrambi RaceIDe BreedIDsono rappresentati in Animal2. Al fine di evitare una discrepanza tra la RaceIDnota in Animal2e una BreedIDche è per un diverso RaceID, ho aggiunto un FK su entrambi RaceID, BreedIDche fa riferimento a UN VINCOLO UNICO di quei campi nella Breedtabella. 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 Breedtabella 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
BreedIDdi 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 Breedsenza avere RaceIDdisponibile.
- Mentre il seguente modello funziona, ha due potenziali difetti riguardo al concetto di condiviso
Breed(e sono il motivo per cui preferisco le tabelle Racespecifiche Breed).
- Si presume implicitamente che TUTTI i valori di
Breedabbiano 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
Breedtra 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 Breedl'entità principale. In questa configurazione la Breedtabella avrebbe le proprietà comuni a tutti i tipi di Breed(proprio come la Animaltabella) e RaceIDrappresenterebbe il tipo di Breed(come nella Animaltabella). Poi si dovrebbe avere tavoli sottoclassi, come BreedCat, BreedDoge 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
CreatedDatecampo verrebbe aggiunto alla Animaltabella. 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
LastModifiedDatecampo verrebbe aggiunto alla Animaltabella e a tutte le tabelle delle sottoclassi. Questo campo viene aggiornato solo se quella particolare tabella viene aggiornata: se si verifica un aggiornamento AnimalCatma non Animalper un particolare AnimalID, verrà impostato solo il LastModifiedDatecampo in AnimalCat.