@Bill Karwin описва три модела на наследяване в книгата си SQL Antipatterns, когато предлага решения за антимодел на SQL Entity-Attribute-Value. Това е кратък преглед:
Наследяване на единична таблица (известно още като наследяване на таблица на йерархия):
Използването на една таблица, както в първата ви опция, е може би най-простият дизайн. Както споменахте, много атрибути, които са специфични за подтип, ще трябва да получат 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
с.
Ето как ще трябва да потърсите всички правила, независимо от типа:
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
.
Смятам, че подходът на таблицата с класове е най-подходящ в повечето ситуации.
Имената на тези три модела идват от книгата на Мартин Фаулър Модели на архитектурата на корпоративните приложения.