Как можно представить наследование в базе данных?


Я думаю о том, как представить сложную структуру в базе данных SQL Server.

рассмотрим приложение, которое должно хранить сведения о семействе объектов, которые разделяют некоторые атрибуты, но имеют много других не общих. Например, пакет коммерческого страхования может включать в себя ответственность, двигатель, имущество и возмещение в рамках одной и той же записи полиса.

это тривиально реализовать в C# и т. д., Поскольку вы можете создать политику с коллекцией Разделы, где раздел наследуется по мере необходимости для различных типов покрытия. Однако, реляционные базы данных не позволяют это легко.

Я вижу, что есть два основных варианта:

  1. создайте таблицу политики, а затем таблицу разделов со всеми необходимыми полями для всех возможных вариантов, большинство из которых будет равно null.

  2. создайте таблицу политики и многочисленные таблицы разделов, по одной для каждого вида покрытие.

обе эти альтернативы кажутся неудовлетворительными, тем более, что необходимо писать запросы по всем разделам, которые будут включать многочисленные соединения или многочисленные нулевые проверки.

какова наилучшая практика для этого сценария?

7 173

7 ответов:

@Билл Karwin описывает три модели наследования в его SQL Antipatterns книга, предлагая решения для SQL Объект-Атрибут-Значение антиобразец. Это краткий обзор:

наследование одной таблицы (aka таблица на наследование иерархии):

использование одной таблицы, как в вашем первом варианте, вероятно, самый простой дизайн. Как вы упомянули, многие атрибуты, относящиеся к подтипу, должны быть учитывая 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 для каждого подтипа. Это может легко привести к ошибкам в вашем приложении, если эта операция будет забыта.

наследование таблиц классов (aka Table Per Type Inheritance):

это решение, что @Дэвид упоминает в другом ответе. Вы создаете единую таблицу для вашего базового класса, которая включает в себя все общие атрибуты. Затем вы создадите определенные таблицы для каждого подтипа, первичный ключ которого также служит внешний ключ к базовой таблице. Пример:

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.

  • добавление нового подтипа требует добавления новой таблицы вместо добавления столбцов к существующему.

  • нет риска, что неподходящий атрибут установлен для определенного подтипа.

  • не нужно .

  • теперь общие атрибуты больше не смешиваются с конкретными атрибутами подтипа.

  • мы можем остаться сухими, в конечном счете. Нет необходимости повторять общие атрибуты для каждой таблицы подтипов при создании таблиц.

  • управление автоматическим приращением id для политик становится проще, потому что это может быть обработано базовой таблицей, а не каждой таблицей подтипов, генерирующей их независимо.

  • поиск всех политик независимо от подтипа теперь становится очень легко: нет UNION s необходимо-просто a SELECT * FROM policies.

я считаю подход к таблице классов наиболее подходящим в большинстве ситуаций.


названия этих трех моделей происходят от Мартин Фаулер книги Шаблоны архитектуры корпоративных приложений.

третий вариант-создать таблицу "политика", а затем таблицу" SectionsMain", в которой хранятся все поля, которые являются общими для всех типов разделов. Затем создайте другие таблицы для каждого типа раздела, которые содержат только поля, которые не являются общими.

решение о том, что лучше, зависит в основном от того, сколько полей у вас есть и как вы хотите написать свой SQL. Они все будут работать. Если у вас есть только несколько полей, то я, вероятно, пойду с #1. С "большим количеством" полей I будет склоняться к #2 или #3.

С предоставленной информацией я бы смоделировал базу данных следующим образом:

политика

  • POLICY_ID (первичный ключ)

обязательства

  • LIABILITY_ID (первичный ключ)
  • POLICY_ID (внешний ключ)

свойства

  • PROPERTY_ID (первичный ключ)
  • POLICY_ID (внешний ключ)

...и так далее, потому что я ожидал там быть различные атрибуты, связанные с каждым разделом политики. В противном случае, может быть один SECTIONS таблица и в дополнение к policy_id, будет section_type_code...

в любом случае, это позволит вам поддерживать дополнительные разделы в политике...

Я не понимаю, что вы считаете неудовлетворительным в этом подходе-это то, как вы храните данные, сохраняя ссылочную целостность и не дублируя данные. Термин "нормированный."..

поскольку SQL установлен на основе, он довольно чужд процедурным / OO концепциям программирования и требует, чтобы код переходил из одной области в другую. ОРМ часто рассматриваются, но они не работают хорошо в больших объемах, сложных системах.

другой способ сделать это, используя INHERITS компонент. Например:

CREATE TABLE person (
    id int ,
    name varchar(20),
    CONSTRAINT pessoa_pkey PRIMARY KEY (id)
);

CREATE TABLE natural_person (
    social_security_number varchar(11),
    CONSTRAINT pessoaf_pkey PRIMARY KEY (id)
) INHERITS (person);


CREATE TABLE juridical_person (
    tin_number varchar(14),
    CONSTRAINT pessoaj_pkey PRIMARY KEY (id)
) INHERITS (person);

таким образом, можно определить наследование между таблицами.

Я склоняюсь к методу №1 (унифицированная таблица разделов), ради эффективного извлечения всех политик со всеми их разделами (что я предполагаю, что ваша система будет делать много).

далее, я не знаю, какую версию SQL Server вы используете, но в 2008+ Разреженные Столбцы помогает оптимизировать производительность в ситуациях, когда многие значения в столбце будут равны нулю.

в конечном счете, вам придется решить, насколько "похожи" разделы политики являются. Если они не отличаются существенно, Я думаю, что более нормализованное решение может быть больше проблем, чем стоит... но только ты можешь сделать этот звонок. :)

кроме того, в решении Daniel Vassallo, если вы используете SQL Server 2016, есть еще одно решение, которое я использовал в некоторых случаях без значительной потери производительности.

вы можете создать только таблицу только с общим полем и добавить один столбец с JSON строка, содержащая все поля подтипа.

я протестировал этот дизайн для управления наследованием, и я очень рад за гибкость, которую я могу использовать в относительном приложение.