Подходящей структурой для этого сценария является модель подкласса / наследования, и она почти идентична концепции, предложенной мною в этом ответе: гетерогенный упорядоченный список значений .
Модель, предложенная в этом вопросе, на самом деле довольно близка в том, что Animalсущность содержит тип (то есть race) и свойства, которые являются общими для всех типов. Однако есть два небольших изменения, которые необходимы:
Удалите поля Cat_ID и Dog_ID из их соответствующих объектов:
Ключевым понятием здесь является то , что все это Animal, независимо от того race: Cat, Dog, Elephant, и так далее. Учитывая , что отправной точкой, какого - либо конкретного raceиз Animalне действительно нужен отдельный идентификатор с:
Animal_IDуникален
- то
Cat, Dogи любые дополнительные raceобъекты , добавленные в будущем не сами по себе, в полной мере представляют собой какой - либо конкретной Animal; они имеют значение только при использовании в сочетании с информацией, содержащейся в родительской сущности Animal.
Следовательно, Animal_IDсвойство в Cat, Dogи т.д. сущности является как PK и FK обратно к Animalлицу.
Различают типы breed:
То, что два свойства имеют одно и то же имя, не обязательно означает, что эти свойства одинаковы, даже если одно и то же имя подразумевает такие отношения. В этом случае то, что у вас есть на самом деле, на самом деле CatBreedи DogBreedкак отдельные «типы»
Начальные заметки
- SQL является специфическим для Microsoft SQL Server (то есть T-SQL). То есть, будьте осторожны с типами данных, так как они не одинаковы во всех RDBMS. Например, я использую,
VARCHARно если вам нужно хранить что-либо за пределами стандартного набора ASCII, вы должны действительно использовать NVARCHAR.
- Идентификационные поля таблиц «типа» (
Race, CatBreedи DogBreed) являются не автоматическим приращением (т.е. Идентичности в терминах T-SQL) , потому что они являются константами приложения (т.е. они являются частью приложения) , которые являются статическими значениями столбца просмотра в элементе базы данных и представлены как enums в C # (или других языках). Если значения добавляются, они добавляются в контролируемых ситуациях. Я резервирую использование полей автоинкремента для пользовательских данных, которые поступают через приложение.
- Соглашение об именах, которое я использую, состоит в том, чтобы называть каждую таблицу подкласса, начиная с имени основного класса, за которым следует имя подкласса. Это помогает упорядочить таблицы, а также четко показывает (не глядя на FK) отношение таблицы подкласса к основной таблице сущностей.
- Пожалуйста, ознакомьтесь с разделом «Окончательное редактирование» в конце для заметки о просмотрах.
«Порода» как «раса» - специфический подход

Этот первый набор таблиц - это таблицы поиска / типов:
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
);
Этот второй листинг является основным объектом «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);
Этот третий набор таблиц является дополнительными объектами подкласса, которые завершают определение каждого Raceиз 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);
Модель, использующая общий breedтип, показана после раздела «Дополнительные примечания».
Дополнительные замечания
- Концепция,
breedкажется, является центром путаницы. Jcolebrand (в комментарии к вопросу) предположил, что breedэто свойство является общим для разных races, и два других ответа интегрированы как таковые в их модели. Это ошибка, однако, потому что значения для breedне разделяются между различными значениями race. Да, я знаю, что две другие предложенные модели пытаются решить эту проблему, сделав raceодного из родителей breed. Хотя это технически решает проблему взаимоотношений, это не помогает решить общий вопрос моделирования о том, что делать с необычными свойствами, и как обрабатывать объект race, у которого нет breed. Но, в случае, если такая собственность гарантированно существует во всехAnimals, я включу опцию для этого (ниже).
- Модели, предложенные Виджайпом и Дэвидом (которые кажутся идентичными), не работают, потому что:
- Они либо
- не допускайте хранения необычных свойств (по крайней мере, для отдельных экземпляров
Animal), или
- требуют, чтобы все свойства для всех
races были сохранены в Animalобъекте, который является очень плоским (и почти нереляционным) способом представления этих данных. Да, люди делают это постоянно, но это означает наличие множества пустых полей в строке для свойств, которые не предназначены для этого конкретного raceИ, зная, какие поля в строке связаны с конкретным raceобъектом этой записи.
- Они не позволяют добавления
raceиз Animalв будущем , которое не имеет breedв собственности. И даже если у ВСЕХ Animalесть breed, это не изменит структуру из-за того, о чем ранее было отмечено breed: это breedзависит от race(то есть breedfor Cat- это не то же самое, что и breedдля Dog).
«Порода» как подход к общей / совместной собственности

Пожалуйста, обратите внимание:
SQL ниже может быть запущен в той же базе данных, что и модель, представленная выше:
RaceТаблицы одно и то же
BreedСтол новый
- Три
Animalтаблицы были добавлены с2
- Даже
Breedбудучи общепринятым в настоящее время свойством, кажется неправильным не Raceуказывать в основной / родительской сущности (даже если это технически корректно с точки зрения отношений). Итак, оба RaceIDи BreedIDпредставлены в Animal2. Чтобы предотвратить несоответствие между RaceIDотмеченными в Animal2и a BreedIDдля другого RaceID, я добавил FK для обоих, RaceID, BreedIDкоторый ссылается на УНИКАЛЬНОЕ ОГРАНИЧЕНИЕ этих полей в Breedтаблице. Я обычно презираю указание ФК на УНИКАЛЬНОЕ ОГРАНИЧЕНИЕ, но вот одна из немногих веских причин для этого. УНИКАЛЬНОЕ ОГРАНИЧЕНИЕ логически является «альтернативным ключом», что делает его действительным для этого использования. Также обратите внимание, что на Breedстоле по-прежнему есть ПК BreedID.
- Причина, по которой нельзя использовать только PK в объединенных полях и не использовать UNIQUE CONSTRAINT, состоит в том, что это позволяет
BreedIDповторять одно и то же для разных значений RaceID.
- Причина того, что PK и UNIQUE CONSTRAINT не переключаются, заключается в том, что это может быть не единственным использованием
BreedID, поэтому все еще должна быть возможность ссылаться на конкретное значение, Breedне имея RaceIDдоступного.
- Хотя следующая модель работает, у нее есть два потенциальных недостатка в отношении концепции общего доступа
Breed(и именно поэтому я предпочитаю таблицы, Raceспецифичные для конкретного пользователя Breed).
- Существует неявное предположение, что ВСЕ значения
Breedимеют одинаковые свойства. В этой модели нет простого способа иметь разнородные свойства между Dog«породами» и Elephant«породами». Тем не менее, есть еще способ сделать это, что указано в разделе «Окончательное редактирование».
- Нет никакого способа разделить
Breedболее чем одну расу. Я не уверен, что это желательно делать (или, возможно, не в концепции животных, но, возможно, в других ситуациях, которые будут использовать этот тип модели), но здесь это невозможно.
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);
Окончательное редактирование (надеюсь ;-)
- Что касается возможности (а затем сложности) обработки разнородных свойств между типами
Breed, то есть можно использовать один и то же подкласс / понятие наследования , но с Breedкак основным субъектом. В этой настройке Breedтаблица будет иметь свойства, общие для всех типов Breed(как и Animalтаблица), и RaceIDбудет представлять тип Breed(такой же, как в Animalтаблице). Тогда вы бы подкласс таблицы , такие как BreedCat, BreedDogи так далее. Для небольших проектов это может считаться «чрезмерным проектированием», но это упоминается как вариант для ситуаций, которые выиграют от этого.
Для обоих подходов иногда помогает создавать представления как ярлыки для полных сущностей. Например, рассмотрим:
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"
- Хотя они не являются частью логических сущностей, довольно часто в таблицах есть поля аудита, чтобы по крайней мере получить представление о том, когда записи вставляются и обновляются. Итак, в практическом плане:
CreatedDateПоле должно быть добавлено к Animalтаблице. Это поле не требуется ни в одной из таблиц подкласса (например AnimalCat), поскольку строки, вставляемые для обеих таблиц, должны выполняться одновременно в транзакции.
LastModifiedDateПоле будет добавлено в Animalтаблицу и все таблицы подкласса. Это поле обновляется только в том случае, если обновляется эта конкретная таблица: если обновление происходит в конкретном случае , AnimalCatно не в Animalнем AnimalID, то будет установлено только LastModifiedDateполе в AnimalCat.