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