Належна структура цього сценарію є підкласовою / спадковою моделлю і майже ідентична концепції, запропонованій у цій відповіді: Гетерогенний упорядкований список цінностей .
Модель, запропонована в цьому питанні, насправді досить близька тим, що 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
);
Цей другий список є основним об'єктом "Тварини":
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, я також включу варіант для цього (нижче).
- Моделі, запропоновані vijayp та DavidN (які здаються ідентичними), не працюють, оскільки:
- Вони також
- не дозволяють зберігати загальні властивості (принаймні, не для окремих примірників будь-яких
Animal
), або
- вимагають, щоб усі властивості для всіх
race
s зберігалися в Animal
об'єкті, що є дуже плоским (і майже нереляційним) способом подання цих даних. Так, люди роблять це постійно, але це означає мати багато полів NULL в рядку для властивостей, які не призначені для цього конкретного race
І, знаючи, які поля в рядку пов'язані з конкретним race
записом.
- Вони не дозволяють додавання
race
з Animal
в майбутньому , яке не має breed
у власності. І навіть якщо ALL Animal
s мають a breed
, це не змінить структуру через те, що раніше було зазначено про breed
: breed
це залежить від race
(тобто breed
для Cat
це не те саме, що breed
для Dog
).
"Порода" як загальний / спільний підхід
Будь ласка, запиши:
Нижче SQL можна запустити в тій самій базі даних, що і модель, представлена вище:
Race
Таблиці одне і те ж
Breed
стіл новий
- До трьох
Animal
таблиць додано а2
- Навіть
Breed
якщо це тепер загальна властивість, здається, це не правильно, що це не було Race
зазначено в основній / материнській структурі (навіть якщо це технічно відносно коректно). Отже, обидва RaceID
і BreedID
представлені в Росії Animal2
. Для того, щоб запобігти невідповідності між RaceID
зазначеним в Animal2
та a, BreedID
що є для іншого RaceID
, я додав FK в обох, RaceID, BreedID
що посилаються на Унікальний обмеження цих полів у Breed
таблиці. Зазвичай я зневажаю вказуючи ФК на Унікальне обмеження, але ось одна з небагатьох поважних причин для цього. УНІКАЛЬНЕ ОБМЕЖЕННЯ логічно є "альтернативним ключем", що робить його дійсним для цього використання. Зверніть також увагу, що в Breed
таблиці все ще є ПК на просто BreedID
.
- Причина того, що не потрібно використовувати просто ПК на комбінованих полях та відсутність Унікального обмеження, полягає в тому, що це дозволило
BreedID
б повторити те саме через різні значення RaceID
.
- Причина не перемикання, на яку оточують PK та Унікальний обмеження, полягає в тому, що це може бути не єдиним використанням
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
.