@ Билл Карвин описывает три модели наследования в своей книге SQL Antipatterns , предлагая решения для SQL Entity-Attribute-Value antipattern. Это краткий обзор:
Наследование в одной таблице (он же таблица на иерархическое наследование):
Использование одной таблицы, как в вашем первом варианте, вероятно, самый простой дизайн. Как вы упомянули, многим атрибутам, относящимся к подтипу, нужно будет присвоить значение 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
не требуется, как в методе с одной таблицей. Тип теперь определяется метаданными: имя таблицы.
Однако эта модель также имеет несколько недостатков:
Общие атрибуты смешиваются с атрибутами, относящимися к подтипу, и простого способа их идентификации нет. База данных также не будет знать.
При определении таблиц вам придется повторять общие атрибуты для каждой таблицы подтипов. Это точно не DRY .
Поиск всех политик независимо от подтипа становится трудным, и для этого потребуется куча UNION
с.
Вот как вам нужно будет запрашивать все политики независимо от типа:
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
для каждого подтипа. Это может легко привести к ошибкам в вашем приложении, если эта операция будет забыта.
Наследование таблиц классов (или Наследование таблиц типов):
Это решение, которое @ Дэвид упоминает в другом ответе . Вы создаете одну таблицу для вашего базового класса, которая включает в себя все общие атрибуты. Затем вы должны создать конкретные таблицы для каждого подтипа, первичный ключ которого также служит внешним ключом для базовой таблицы. Пример:
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
s - просто SELECT * FROM policies
.
Я считаю подход таблицы классов наиболее подходящим в большинстве ситуаций.
Названия этих трех моделей взяты из Книги Мартина Фаулера Шаблоны архитектуры корпоративных приложений .