La estructura adecuada para este escenario es un modelo de Subclase / Herencia, y es casi idéntico al concepto que propuse en esta respuesta: Lista de valores ordenada heterogénea .
El modelo propuesto en esta pregunta es en realidad bastante cercano, ya que la Animalentidad contiene el tipo (es decir race) y las propiedades que son comunes en todos los tipos. Sin embargo, hay dos cambios menores que se necesitan:
Elimine los campos Cat_ID y Dog_ID de sus respectivas entidades:
El concepto clave aquí es que todo lo que es una Animal, sin tener en cuenta race: Cat, Dog, Elephant, y así sucesivamente. Dado ese punto de partida, cualquier particular racede Animalrealmente no necesita un identificador separado ya que:
- el
Animal_IDes único
- the
Cat, Dogy cualquier raceentidad adicional agregada en el futuro, por sí mismas, no representan completamente ningún particular Animal; que sólo tienen significado cuando se utiliza en combinación con la información contenida en la entidad matriz, Animal.
Por lo tanto, la Animal_IDpropiedad en el Cat, Dog, etc entidades es a la vez el PK y la parte posterior FK a la Animalentidad.
Diferenciar entre tipos de breed:
El hecho de que dos propiedades compartan el mismo nombre no significa necesariamente que esas propiedades sean las mismas, incluso si el nombre es el mismo implica tal relación. En este caso, lo que realmente tiene es en realidad CatBreedy DogBreedcomo "tipos" separados
Notas iniciales
- El SQL es específico de Microsoft SQL Server (es decir, es T-SQL). Es decir, tenga cuidado con los tipos de datos, ya que no son los mismos en todos los RDBMS. Por ejemplo, lo estoy usando,
VARCHARpero si necesita almacenar algo fuera del conjunto ASCII estándar, realmente debería usarlo NVARCHAR.
- Los campos de ID de las tablas de "tipo" (
Race, CatBreedy DogBreed) no se incrementan automáticamente (es decir, IDENTITY en términos de T-SQL) porque son constantes de aplicación (es decir, son parte de la aplicación) que son valores de búsqueda estáticos en base de datos y se representan como enums en C # (u otros lenguajes). Si se agregan valores, se agregan en situaciones controladas. Me reservo el uso de campos de incremento automático para los datos del usuario que ingresan a través de la aplicación.
- La convención de nomenclatura que uso es nombrar cada tabla de subclase comenzando con el nombre de la clase principal seguido del nombre de la subclase. Esto ayuda a organizar las tablas y también indica claramente (sin mirar las FK) la relación de la tabla de subclase con la tabla de entidad principal.
- Consulte la sección "Edición final" al final para obtener una nota sobre Vistas.
"Raza" como "Raza" - Enfoque específico

Este primer conjunto de tablas son las tablas de búsqueda / tipos:
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
);
Este segundo listado es la principal entidad "Animal":
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);
Este tercer conjunto de tablas son las entidades de subclase complementarias que completan la definición de cada una 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);
El modelo que usa un breedtipo compartido se muestra después de la sección "Notas adicionales".
Notas adicionales
- El concepto de
breedparece ser un punto focal para la confusión. Fue sugerido por jcolebrand (en un comentario sobre la pregunta) que breedes una propiedad compartida entre los diferentes correos electrónicos race, y las otras dos respuestas lo tienen integrado como tal en sus modelos. Sin embargo, esto es un error, porque los valores de breedno se comparten entre los diferentes valores de race. Sí, soy consciente de que los otros dos modelos propuestos intentan resolver este problema haciendo raceun padre de breed. Si bien eso resuelve técnicamente el problema de la relación, no ayuda a resolver la pregunta general de modelado sobre qué hacer con las propiedades no comunes, ni cómo manejar una raceque no tiene a breed. Pero, en el caso de que se garantizara que tal propiedad existiera en todosAnimals, también incluiré una opción para eso (a continuación).
- Los modelos propuestos por vijayp y DavidN (que parecen ser idénticos) no funcionan porque:
- Ellos tampoco
- no permita que se almacenen propiedades no comunes (al menos no para instancias individuales de ninguna
Animal), o
- requieren que todas las propiedades de todos los
races se almacenen en la Animalentidad, que es una forma muy plana (y casi no relacional) de representar estos datos. Sí, la gente hace esto todo el tiempo, pero significa tener muchos campos NULL por fila para las propiedades que no están destinadas a ese particular raceY saber qué campos por fila están asociados con el particular racede ese registro.
- No permiten agregar una
racede Animalen el futuro que no tiene breedcomo propiedad. E incluso si TODOS Animaltienen una breed, eso no cambiaría la estructura debido a lo que se ha señalado anteriormente breed: eso breeddepende de race(es decir, breedporque Catno es lo mismo que breedpara Dog).
"Raza" como enfoque de propiedad común / compartida

Tenga en cuenta:
El SQL a continuación se puede ejecutar en la misma base de datos que el modelo presentado anteriormente:
- La
Racemesa es igual
- La
Breedmesa es nueva
- Las tres
Animaltablas han sido agregadas con un2
- Incluso
Breedsiendo una propiedad ahora común, no parece correcto no haberlo Racenotado en la entidad principal / principal (incluso si es técnicamente correcto desde el punto de vista relacional). Entonces, ambos RaceIDy BreedIDestán representados en Animal2. Para evitar una falta de coincidencia entre lo RaceIDanotado en Animal2y lo BreedIDque es para un diferente RaceID, he agregado un FK en ambos RaceID, BreedIDque hace referencia a una RESTRICCIÓN ÚNICA de esos campos en la Breedtabla. Por lo general, desprecio señalar un FK a una RESTRICCIÓN ÚNICA, pero esta es una de las pocas razones válidas para hacerlo. Una RESTRICCIÓN ÚNICA es lógicamente una "Clave alternativa", que la hace válida para este uso. Tenga en cuenta también que la Breedtabla todavía tiene un PK en solo BreedID.
- La razón para no ir solo con un PK en los campos combinados y sin RESTRICCIONES ÚNICAS es que permitiría
BreedIDque se repita lo mismo en diferentes valores de RaceID.
- La razón para no cambiar la PK y la RESTRICCIÓN ÚNICA es que este podría no ser el único uso de
BreedID, por lo que aún debería ser posible hacer referencia a un valor específico de Breedsin tener RaceIDdisponible.
- Si bien el siguiente modelo funciona, tiene dos fallas potenciales con respecto al concepto de un compartido
Breed(y es por eso que prefiero las tablas Raceespecíficas Breed).
- Hay una suposición implícita de que TODOS los valores de
Breedtienen las mismas propiedades. No hay una manera fácil en este modelo de tener propiedades dispares entre Dog"razas" y Elephant"razas". Sin embargo, todavía hay una manera de hacerlo, que se observa en la sección "Edición final".
- No hay forma de compartir una
Breeden más de una carrera. No estoy seguro de si es deseable hacerlo (o tal vez no en el concepto de animales, pero posiblemente en otras situaciones que estarían usando este tipo de modelo), pero aquí no es posible.
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);
Edición final (con suerte ;-)
- Con respecto a la posibilidad (y luego la dificultad) de manejar propiedades dispares entre tipos de
Breed, es posible emplear el mismo concepto de subclase / herencia pero con Breedla entidad principal. En esta configuración, la Breedtabla tendría las propiedades comunes a todos los tipos de Breed(al igual que la Animaltabla) y RaceIDrepresentaría el tipo de Breed(igual que en la Animaltabla). De allí tendría que tener tablas de subclase como BreedCat, BreedDogy así sucesivamente. Para proyectos más pequeños esto podría considerarse "sobre-ingeniería", pero se menciona como una opción para situaciones que se beneficiarían de él.
Para ambos enfoques, a veces ayuda crear Vistas como accesos directos a las entidades completas. Por ejemplo, considere:
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"
- Si bien no forma parte de las entidades lógicas, es bastante común tener campos de auditoría en las tablas para al menos tener una idea de cuándo se insertan y actualizan los registros. Entonces, en términos prácticos:
- Se
CreatedDateagregaría un campo a la Animaltabla. Este campo no es necesario en ninguna de las tablas de subclase (p AnimalCat. Ej. ), Ya que las filas que se insertan para ambas tablas deben realizarse al mismo tiempo dentro de una transacción.
- Se
LastModifiedDateagregaría un campo a la Animaltabla y a todas las tablas de subclase. Este campo se actualiza solo si esa tabla en particular se actualiza: si se produce una actualización AnimalCatpero no en Animaluna en particular AnimalID, solo se establecerá el LastModifiedDatecampo en AnimalCat.