A estrutura apropriada para esse cenário é um modelo de subclasse / herança e é quase idêntica ao conceito que propus nesta resposta: Lista de valores ordenados heterogêneos .
O modelo proposto nesta questão é bastante próximo, pois a Animal
entidade contém o tipo (ou seja race
) e as propriedades comuns a todos os tipos. No entanto, existem duas pequenas alterações necessárias:
Remova os campos Cat_ID e Dog_ID de suas respectivas entidades:
O conceito chave aqui é que tudo é um Animal
, independentemente race
: Cat
, Dog
, Elephant
, e assim por diante. Dado que o ponto de partida, qualquer particular, race
de Animal
não realmente precisa de um identificador separado desde:
- o
Animal_ID
é único
- o
Cat
,, Dog
e quaisquer race
entidades adicionais adicionadas no futuro, por si só, não representam totalmente nenhum detalhe Animal
; apenas têm significado quando utilizados em combinação com as informações contidas na entidade - mãe Animal
.
Assim, a Animal_ID
propriedade no Cat
, Dog
, etc entidades é tanto o PK e as costas FK à Animal
entidade.
Diferencie entre tipos de breed
:
Só porque duas propriedades compartilham o mesmo nome não significa necessariamente que essas propriedades são iguais, mesmo que o nome que seja o mesmo implique esse relacionamento. Nesse caso, o que você realmente tem é realmente CatBreed
e DogBreed
como "tipos" separados
Notas iniciais
- O SQL é específico para o Microsoft SQL Server (ou seja, é T-SQL). Ou seja, tenha cuidado com os tipos de dados, pois eles não são os mesmos em todos os RDBMSs. Por exemplo, estou usando,
VARCHAR
mas se você precisar armazenar algo fora do conjunto ASCII padrão, você deve realmente usá-lo NVARCHAR
.
- Os campos de identificação das mesas "tipo" (
Race
, CatBreed
e DogBreed
) são não (ou seja, IDENTIDADE, em termos de T-SQL) auto-incremental, porque eles são constantes aplicação (ou seja, são parte da aplicação) que são valores de pesquisa estáticos no banco de dados e são representados como enum
s em C # (ou em outros idiomas). Se valores são adicionados, eles são adicionados em situações controladas. Reservo o uso de campos de incremento automático para dados do usuário que chegam através do aplicativo.
- A convenção de nomenclatura que eu uso é nomear cada tabela de subclasse começando com o nome da classe principal seguido pelo nome da subclasse. Isso ajuda a organizar as tabelas e indica claramente (sem olhar para os FKs) o relacionamento da tabela da subclasse com a tabela principal da entidade.
- Consulte a seção "Edição final" no final para obter uma observação sobre as visualizações.
"Raça" como abordagem específica de "raça"
Este primeiro conjunto de tabelas são as tabelas de pesquisa / 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
);
Esta segunda listagem é a principal entidade "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 terceiro conjunto de tabelas são as entidades de subclasse complementares que completam a definição de cada um Race
dos seguintes 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);
O modelo que usa um breed
tipo compartilhado é mostrado após a seção "Notas adicionais".
Notas Adicionais
- O conceito de
breed
parece ser um ponto focal de confusão. Foi sugerido por jcolebrand (em um comentário sobre a pergunta) que breed
é uma propriedade compartilhada entre os diferentes race
s, e as outras duas respostas a integram como tal em seus modelos. Isso é um erro, no entanto, porque os valores para breed
não são compartilhados entre os diferentes valores de race
. Sim, estou ciente de que os outros dois modelos propostos tentam resolver esse problema criando race
um pai breed
. Embora isso resolva tecnicamente o problema do relacionamento, não ajuda a resolver a questão geral da modelagem sobre o que fazer com propriedades não comuns, nem como lidar com um race
que não possui um breed
. Porém, no caso de garantir a existência de tal propriedade em todos osAnimal
s, incluirei uma opção para isso também (abaixo).
- Os modelos propostos por vijayp e DavidN (que parecem idênticos) não funcionam porque:
- Eles também
- não permita que propriedades não comuns sejam armazenadas (pelo menos não para instâncias individuais de nenhuma
Animal
), ou
- exigem que todas as propriedades para todos os
race
s sejam armazenadas na Animal
entidade, que é uma maneira muito simples (e quase não relacional) de representar esses dados. Sim, as pessoas fazem isso o tempo todo, mas isso significa ter muitos campos NULL por linha para as propriedades que não são destinadas a esse determinado race
AND sabendo quais campos por linha estão associados ao particular race
desse registro.
- Eles não permitem adicionar um
race
de Animal
no futuro que não tem breed
como uma propriedade. E mesmo que TODOS os Animal
s tenham um breed
, isso não mudaria a estrutura devido ao que foi observado anteriormente breed
: isso breed
depende do race
(isto é, breed
para Cat
não é a mesma coisa que breed
para Dog
).
"Raça" como abordagem de propriedade comum / compartilhada
Observe:
O SQL abaixo pode ser executado no mesmo banco de dados que o modelo apresentado acima:
- A
Race
tabela é a mesma
- A
Breed
tabela é nova
- As três
Animal
tabelas foram anexadas com um2
- Mesmo
Breed
sendo uma propriedade agora comum, não parece certo não ter Race
notado na entidade principal / principal (mesmo que tecnicamente seja relacionalmente correta). Então, ambos RaceID
e BreedID
são representados em Animal2
. Para evitar uma incompatibilidade entre o RaceID
observado em Animal2
e o BreedID
que é diferente RaceID
, adicionei um FK em ambos os RaceID, BreedID
que referencia uma RESTRIÇÃO ÚNICA desses campos na Breed
tabela. Eu geralmente desprezo apontar um FK para uma restrição exclusiva, mas aqui está uma das poucas razões válidas para fazer isso. Uma restrição exclusiva é logicamente uma "chave alternativa", que a torna válida para esse uso. Observe também que a Breed
tabela ainda possui um PK apenas BreedID
.
- O motivo para não usar apenas uma PK nos campos combinados e nenhuma restrição UNIQUE é que permitiria que o mesmo
BreedID
fosse repetido entre diferentes valores de RaceID
.
- O motivo para não alternar entre o PK e o UNIQUE CONSTRAINT é que esse pode não ser o único uso
BreedID
, portanto ainda deve ser possível fazer referência a um valor específico Breed
sem ter o RaceID
disponível.
- Embora o modelo a seguir funcione, ele tem duas falhas em potencial em relação ao conceito de um compartilhamento
Breed
(e é por isso que eu prefiro as tabelas Race
específicas Breed
).
- Há uma suposição implícita de que TODOS os valores
Breed
têm as mesmas propriedades. Não há uma maneira fácil neste modelo de ter propriedades díspares entre Dog
"raças" e Elephant
"raças". No entanto, ainda existe uma maneira de fazer isso, que é anotado na seção "Edição final".
- Não há como compartilhar uma
Breed
em mais de uma corrida. Não tenho certeza se isso é desejável (ou talvez não no conceito de animais, mas possivelmente em outras situações que estariam usando esse tipo de modelo), mas não é possível aqui.
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);
Edição final (espero ;-)
- Com relação à possibilidade (e depois à dificuldade) de lidar com propriedades díspares entre tipos de
Breed
, é possível empregar o mesmo conceito de subclasse / herança, mas com Breed
a entidade principal. Nesta configuração, a Breed
tabela teria as propriedades comuns a todos os tipos de Breed
(assim como a Animal
tabela) e RaceID
representaria o tipo de Breed
(o mesmo que na Animal
tabela). Então você teria tabelas subclasses, tais como BreedCat
, BreedDog
e assim por diante. Para projetos menores, isso pode ser considerado "excesso de engenharia", mas está sendo mencionado como uma opção para situações que se beneficiariam com isso.
Para ambas as abordagens, às vezes ajuda a criar Views como atalhos para as entidades completas. Por exemplo, 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"
- Embora não faça parte das entidades lógicas, é bastante comum ter campos de auditoria nas tabelas para ter uma noção de quando os registros estão sendo inseridos e atualizados. Então, em termos práticos:
- Um
CreatedDate
campo seria adicionado à Animal
tabela. Este campo não é necessário em nenhuma das tabelas de subclasse (por exemplo AnimalCat
), pois as linhas que estão sendo inseridas nas duas tabelas devem ser executadas ao mesmo tempo em uma transação.
- Um
LastModifiedDate
campo seria adicionado à Animal
tabela e a todas as tabelas de subclasse. Este campo é atualizado apenas se essa tabela específica for atualizada: se uma atualização ocorrer, AnimalCat
mas não dentro Animal
de uma determinada AnimalID
, somente o LastModifiedDate
campo AnimalCat
será definido.