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

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

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

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

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

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

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

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

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

7 ответов

Решение

Bill Karwin описывает три модели наследования в своей книге " Bill Karwin 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,

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


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

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

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

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

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

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

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

ПОЛИТИКА

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

ОБЯЗАННОСТИ

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

СВОЙСТВА

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

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

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

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

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

Другой способ сделать это, используя 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);

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

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

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

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

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

Другие вопросы по тегам