@ Билл Карвин описывает три модели наследования в своей книге « Антипаттерны SQL» , предлагая решения для антипаттерна « Значение сущности SQL» . Это краткий обзор:
Наследование в одной таблице (или Наследование таблиц в иерархии):
Использование одной таблицы, как в первом варианте, является, вероятно, самым простым дизайном. Как вы упомянули, многим атрибутам, относящимся к подтипу, необходимо присвоить NULL
значение в строках, где эти атрибуты не применяются. В этой модели у вас будет одна таблица политик, которая будет выглядеть примерно так:
+------+---------------------+----------+----------------+------------------+
| id | date_issued | type | vehicle_reg_no | property_address |
+------+---------------------+----------+----------------+------------------+
| 1 | 2010-08-20 12:00:00 | MOTOR | 01-A-04004 | NULL |
| 2 | 2010-08-20 13:00:00 | MOTOR | 02-B-01010 | NULL |
| 3 | 2010-08-20 14:00:00 | PROPERTY | NULL | Oxford Street |
| 4 | 2010-08-20 15:00:00 | MOTOR | 03-C-02020 | NULL |
+------+---------------------+----------+----------------+------------------+
\------ COMMON FIELDS -------/ \----- SUBTYPE SPECIFIC FIELDS -----/
Простота дизайна - это плюс, но основные проблемы с этим подходом заключаются в следующем:
Когда дело доходит до добавления новых подтипов, вам придется изменить таблицу, чтобы разместить атрибуты, которые описывают эти новые объекты. Это может быстро стать проблематичным, если у вас много подтипов или если вы планируете добавлять подтипы на регулярной основе.
База данных не сможет принудительно определить, какие атрибуты применяются, а какие нет, поскольку нет метаданных, которые бы определяли, какие атрибуты принадлежат каким подтипам.
Вы также не можете применять NOT NULL
атрибуты подтипа, которые должны быть обязательными. Вам придется справиться с этим в вашем приложении, что в целом не идеально.
Наследование бетонного стола:
Другой подход к решению проблемы наследования заключается в создании новой таблицы для каждого подтипа, повторяя все общие атрибуты в каждой таблице. Например:
--// Table: policies_motor
+------+---------------------+----------------+
| id | date_issued | vehicle_reg_no |
+------+---------------------+----------------+
| 1 | 2010-08-20 12:00:00 | 01-A-04004 |
| 2 | 2010-08-20 13:00:00 | 02-B-01010 |
| 3 | 2010-08-20 15:00:00 | 03-C-02020 |
+------+---------------------+----------------+
--// Table: policies_property
+------+---------------------+------------------+
| id | date_issued | property_address |
+------+---------------------+------------------+
| 1 | 2010-08-20 14:00:00 | Oxford Street |
+------+---------------------+------------------+
Этот дизайн в основном решит проблемы, определенные для метода единой таблицы:
Обязательные атрибуты теперь могут быть применены с помощью NOT NULL
.
Добавление нового подтипа требует добавления новой таблицы вместо добавления столбцов к существующей.
Также нет риска, что для определенного подтипа будет установлен неподходящий атрибут, такой как vehicle_reg_no
поле для политики свойств.
type
Атрибут не нужен, как в методе с одной таблицей. Тип теперь определяется метаданными: имя таблицы.
Однако эта модель также имеет несколько недостатков:
Общие атрибуты смешиваются с определенными атрибутами подтипа, и нет простого способа их идентифицировать. База данных тоже не будет знать.
При определении таблиц вам придется повторять общие атрибуты для каждой таблицы подтипов. Это определенно не СУХОЙ .
Поиск всех политик, независимо от подтипа, становится трудным и потребует кучу UNION
s.
Вот как вам нужно будет запрашивать все политики независимо от типа:
SELECT date_issued, other_common_fields, 'MOTOR' AS type
FROM policies_motor
UNION ALL
SELECT date_issued, other_common_fields, 'PROPERTY' AS type
FROM policies_property;
Обратите внимание, что добавление новых подтипов потребовало бы изменения вышеуказанного запроса с дополнительным UNION ALL
для каждого подтипа. Это может легко привести к ошибкам в вашем приложении, если эта операция будет забыта.
Наследование таблиц классов (или таблица наследования типов):
Это решение, которое @David упоминает в другом ответе . Вы создаете единую таблицу для своего базового класса, которая включает в себя все общие атрибуты. Затем вы должны создать конкретные таблицы для каждого подтипа, первичный ключ которого также служит внешним ключом для базовой таблицы. Пример:
CREATE TABLE policies (
policy_id int,
date_issued datetime,
-- // other common attributes ...
);
CREATE TABLE policy_motor (
policy_id int,
vehicle_reg_no varchar(20),
-- // other attributes specific to motor insurance ...
FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);
CREATE TABLE policy_property (
policy_id int,
property_address varchar(20),
-- // other attributes specific to property insurance ...
FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);
Это решение решает проблемы, выявленные в двух других проектах:
Обязательные атрибуты могут быть применены с NOT NULL
.
Добавление нового подтипа требует добавления новой таблицы вместо добавления столбцов к существующей.
Нет риска, что для определенного подтипа установлен неподходящий атрибут.
Нет необходимости в type
атрибуте.
Теперь общие атрибуты больше не смешиваются с определенными атрибутами подтипа.
Мы можем остаться сухими, наконец. При создании таблиц нет необходимости повторять общие атрибуты для каждой таблицы подтипов.
Управление автоинкрементом id
для политик становится проще, потому что это может обрабатываться базовой таблицей, а не каждой таблицей подтипов, генерирующей их независимо.
Поиск всех политик, независимо от подтипа, теперь становится очень простым: нет UNION
необходимости - просто а SELECT * FROM policies
.
Я считаю подход таблицы классов наиболее подходящим в большинстве ситуаций.
Названия этих трех моделей взяты из книги Мартина Фаулера « Шаблоны архитектуры корпоративных приложений» .