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 Animal
entidad 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 race
de Animal
realmente no necesita un identificador separado ya que:
- el
Animal_ID
es único
- the
Cat
, Dog
y cualquier race
entidad 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_ID
propiedad en el Cat
, Dog
, etc entidades es a la vez el PK y la parte posterior FK a la Animal
entidad.
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 CatBreed
y DogBreed
como "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,
VARCHAR
pero 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
, CatBreed
y 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 enum
s 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 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);
El modelo que usa un breed
tipo compartido se muestra después de la sección "Notas adicionales".
Notas adicionales
- El concepto de
breed
parece ser un punto focal para la confusión. Fue sugerido por jcolebrand (en un comentario sobre la pregunta) que breed
es 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 breed
no se comparten entre los diferentes valores de race
. Sí, soy consciente de que los otros dos modelos propuestos intentan resolver este problema haciendo race
un 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 race
que no tiene a breed
. Pero, en el caso de que se garantizara que tal propiedad existiera en todosAnimal
s, 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
race
s se almacenen en la Animal
entidad, 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 race
Y saber qué campos por fila están asociados con el particular race
de ese registro.
- No permiten agregar una
race
de Animal
en el futuro que no tiene breed
como propiedad. E incluso si TODOS Animal
tienen una breed
, eso no cambiaría la estructura debido a lo que se ha señalado anteriormente breed
: eso breed
depende de race
(es decir, breed
porque Cat
no es lo mismo que breed
para 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
Race
mesa es igual
- La
Breed
mesa es nueva
- Las tres
Animal
tablas han sido agregadas con un2
- Incluso
Breed
siendo una propiedad ahora común, no parece correcto no haberlo Race
notado en la entidad principal / principal (incluso si es técnicamente correcto desde el punto de vista relacional). Entonces, ambos RaceID
y BreedID
están representados en Animal2
. Para evitar una falta de coincidencia entre lo RaceID
anotado en Animal2
y lo BreedID
que es para un diferente RaceID
, he agregado un FK en ambos RaceID, BreedID
que hace referencia a una RESTRICCIÓN ÚNICA de esos campos en la Breed
tabla. 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 Breed
tabla 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
BreedID
que 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 Breed
sin tener RaceID
disponible.
- 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 Race
específicas Breed
).
- Hay una suposición implícita de que TODOS los valores de
Breed
tienen 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
Breed
en 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 Breed
la entidad principal. En esta configuración, la Breed
tabla tendría las propiedades comunes a todos los tipos de Breed
(al igual que la Animal
tabla) y RaceID
representaría el tipo de Breed
(igual que en la Animal
tabla). De allí tendría que tener tablas de subclase como BreedCat
, BreedDog
y 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
CreatedDate
agregaría un campo a la Animal
tabla. 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
LastModifiedDate
agregaría un campo a la Animal
tabla 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 AnimalCat
pero no en Animal
una en particular AnimalID
, solo se establecerá el LastModifiedDate
campo en AnimalCat
.