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

131

Каковы лучшие практики моделирования наследования в базах данных?

Каковы компромиссы (например, возможность запросов)?

(Меня больше всего интересуют SQL Server и .NET, но я также хочу понять, как другие платформы решают эту проблему.)

Даже Миен
источник
14
Если вас интересуют «лучшие практики», большинство ответов просто неверны. Лучшая практика диктует, что RDb и приложение независимы; у них совершенно разные критерии дизайна. Поэтому «моделирование наследования» в базе данных (или моделирование RDb для соответствия одному приложению или языку приложения) является очень плохой практикой, неинформированной и нарушает основные правила проектирования RDb и парализует их.
PerformanceDBA
возможный дубликат чего- то вроде наследования в дизайне базы данных
Стив Чемберс,
6
@PerformanceDBA Итак, что вы предлагаете, чтобы избежать наследования в модели БД? Допустим, у нас есть 50 разных учителей, и мы хотим связать этого конкретного учителя с классом. Как бы вы этого достигли, не имея наследства?
svlada
1
@svlada. Это просто реализовать в RDb, поэтому требуется «наследование». Задайте вопрос, включите таблицу defns и пример, и я отвечу подробно. Если вы сделаете это в условиях объектно-ориентированного подхода, это будет королевский беспорядок.
PerformanceDBA

Ответы:

162

Есть несколько способов смоделировать наследование в базе данных. Что вы выберете, зависит от ваших потребностей. Вот несколько вариантов:

Таблица по типу (TPT)

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

Так, например:

class Person {
    public int ID;
    public string FirstName;
    public string LastName;
}

class Employee : Person {
    public DateTime StartDate;
}

Результатом станут такие таблицы:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK, FK)
datetime startdate

Таблица на иерархию (TPH)

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

Учитывая приведенные выше классы, вы получите следующую таблицу:

table Person
------------
int id (PK)
int rowtype (0 = "Person", 1 = "Employee")
string firstname
string lastname
datetime startdate

Для любых строк, которые имеют тип 0 (человек), начальная дата всегда будет нулевой.

Стол на бетон (TPC)

Каждый класс имеет свою собственную полностью сформированную таблицу без ссылок на какие-либо другие таблицы.

Учитывая приведенные выше классы, вы получите следующие таблицы:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate
Брэд Уилсон
источник
23
«Что вы выберете, зависит от ваших потребностей» - уточните, пожалуйста, поскольку я думаю, что причины выбора составляют суть вопроса.
Alex
12
См. Мой комментарий к вопросу. Использование забавных новых имен для существующих технических терминов Rdb приводит к путанице. «TPT» - это супертип-подтип. "TPH" ненормализовано, грубая ошибка. «TPH» еще менее нормализовано, еще одна грубая ошибка.
PerformanceDBA
45
Только администратор базы данных может предположить, что денормализация всегда является ошибкой. :)
Брэд Уилсон
7
Хотя я признаю, что денормализация приводит к увеличению производительности, в некоторых случаях это полностью связано с неполным (или отсутствующим) разделением между логической и физической структурой данных в СУБД. К сожалению, большинство коммерческих СУБД страдают от этой проблемы. @PerformanceDBA верна. Недонормализация - это ошибка суждения, приносящая в жертву согласованность данных ради скорости. К сожалению, это выбор, который не пришлось бы делать администратору баз данных или разработчику, если бы СУБД была спроектирована правильно. Для справки, я не администратор базы данных.
Kenneth Cochran
6
@ Брэд Уилсон. Только разработчик может денормализовать "для производительности" или иначе. Часто это не денормализация, правда ненормализация. То, что де-нормализация или ненормализация - это ошибка, это факт, подтвержденный теорией и переживаемый миллионами, это не «предположение».
PerformanceDBA
133

Правильный дизайн базы данных - это не что иное, как правильный дизайн объекта.

Если вы планируете использовать базу данных для чего-либо, кроме простой сериализации ваших объектов (например, отчетов, запросов, использования нескольких приложений, бизнес-аналитики и т. Д.), То я не рекомендую какое-либо простое отображение объектов в таблицы.

Многие люди думают о строке в таблице базы данных как о сущности (я много лет размышлял об этом), но строка не является сущностью. Это предложение. Отношение базы данных (то есть таблица) представляет собой некоторую констатацию фактов о мире. Наличие строки указывает на то, что факт истинен (и, наоборот, ее отсутствие указывает на ложность факта).

При таком понимании вы можете увидеть, что один тип в объектно-ориентированной программе может храниться в десятке различных отношений. И множество типов (объединенных наследованием, ассоциацией, агрегацией или полностью неаффилированными) могут частично храниться в одном отношении.

Лучше всего спросить себя, какие факты вы хотите сохранить, на какие вопросы вы хотите получить ответы, какие отчеты вы хотите создавать.

После создания правильного дизайна БД можно легко создавать запросы / представления, которые позволят вам сериализовать ваши объекты в эти отношения.

Пример:

В системе бронирования отелей вам может потребоваться сохранить тот факт, что Джейн Доу забронировала номер в Seaview Inn на 10-12 апреля. Это атрибут организации-клиента? Это атрибут гостиничного объекта? Является ли это субъектом бронирования с объектами недвижимости, включающими клиента и отель? В объектно-ориентированной системе это может быть что угодно или все это. В базе данных ничего из этого нет. Это просто факт.

Чтобы увидеть разницу, рассмотрите следующие два вопроса. (1) Сколько бронирований отелей у Джейн Доу на следующий год? (2) Сколько номеров забронировано на 10 апреля в отеле Seaview Inn?

В объектно-ориентированной системе запрос (1) является атрибутом объекта клиента, а запрос (2) является атрибутом объекта отеля. Это объекты, которые будут отображать эти свойства в своих API. (Хотя, очевидно, что внутренние механизмы, с помощью которых получаются эти значения, могут включать ссылки на другие объекты.)

В системе реляционной базы данных оба запроса будут проверять отношение резервирования, чтобы получить их номера, и концептуально нет необходимости возиться с какой-либо другой «сущностью».

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

Джеффри Л Уитледж
источник
12
+1 Наконец, остров истинных знаний в море невежества (и отказа учиться чему-либо за пределами их возможностей). Согласитесь, это не волшебство: если RDb разработан с использованием принципов RDb, легко «сопоставить» или «спроектировать» любой «класс». Принуждение RDb к требованиям на основе классов просто неверно.
PerformanceDBA
2
Интересный ответ. Как бы вы посоветовали смоделировать пример "человек-сотрудник" в принятом ответе?
sevenforce
2
@ sevenforce-Дизайн БД действительно зависит от требований системы, которые не указаны. Для принятия решения не хватает информации. Во многих случаях что-то похожее на схему «таблица на тип» может быть уместным, если не подчиняться. Например, start-date, вероятно, является хорошим свойством для объекта Employee, но в базе данных это действительно должно быть поле в таблице Employment, так как человек может быть нанят несколько раз с несколькими датами начала. Это не имеет значения для объектов (которые будут использовать самые последние), но важно для базы данных.
Джеффри Л. Уитледж
2
Конечно, мой вопрос был в основном о способе моделирования наследования. Извините, что было недостаточно ясно. Спасибо. Как вы упомянули, скорее всего, должна быть Employmentтаблица, в которой собраны все вакансии с датами их начала. Итак, если Employerважно знать текущую дату начала работы для a , это может быть подходящим вариантом использования для a View, который включает это свойство путем запроса? (примечание: кажется, из-за '-' сразу после моего ника я не получил никакого уведомления о вашем комментарии)
sevenforce
5
Это настоящая жемчужина ответа. Потребуется некоторое время, чтобы по-настоящему погрузиться в нее, и некоторые упражнения, чтобы разобраться в ней правильно, но это уже повлияло на мой мыслительный процесс при проектировании реляционных баз данных.
MarioDS
9

Короткий ответ: нет.

Если вам нужно сериализовать ваши объекты, используйте ORM или, что еще лучше, что-то вроде activerecord или prevaylence.

Если вам нужно хранить данные, храните их реляционным способом (будьте осторожны с тем, что вы храните, и обращайте внимание на то, что только что сказал Джеффри Л. Уитледж), а не на тот, который зависит от дизайна вашего объекта.

Marcin
источник
3
+1 Попытка смоделировать наследование в базе данных - пустая трата хороших реляционных ресурсов.
Daniel Spiewak
7

Как сказал Брэд Уилсон, вам подходят шаблоны TPT, TPH и TPC. Но пара замечаний:

  • дочерние классы, унаследованные от базового класса, могут рассматриваться как слабые сущности для определения базового класса в базе данных, что означает, что они зависят от своего базового класса и не могут существовать без него. Я много раз видел, что уникальные идентификаторы хранятся для каждой дочерней таблицы, а также сохраняются FK в родительской таблице. Одного FK вполне достаточно, а еще лучше включить каскад при удалении для FK-отношения между дочерней и базовой таблицами.

  • В TPT, видя только записи базовой таблицы, вы не можете определить, какой дочерний класс представляет запись. Это иногда необходимо, когда вы хотите загрузить список всех записей (не делая select для каждой дочерней таблицы). Один из способов справиться с этим - иметь один столбец, представляющий тип дочернего класса (аналогично полю rowType в TPH), поэтому каким-то образом смешивайте TPT и TPH.

Допустим, мы хотим создать базу данных, содержащую следующую диаграмму классов фигур:

public class Shape {
int id;
Color color;
Thickness thickness;
//other fields
}

public class Rectangle : Shape {
Point topLeft;
Point bottomRight;
}

public class Circle : Shape {
Point center;
int radius;
}

Дизайн базы данных для вышеуказанных классов может быть таким:

table Shape
-----------
int id; (PK)
int color;
int thichkness;
int rowType; (0 = Rectangle, 1 = Circle, 2 = ...)

table Rectangle
----------
int ShapeID; (FK on delete cascade)
int topLeftX;
int topLeftY;
int bottomRightX;
int bottomRightY;

table Circle
----------
int ShapeID; (FK on delete cascade)  
int centerX;
int center;
int radius;
imang
источник
4

Существует два основных типа наследования, которые вы можете настроить в БД: таблица на объект и таблица на иерархию.

Таблица для каждой сущности - это то место, где у вас есть базовая таблица сущностей, которая имеет общие свойства всех дочерних классов. Затем для каждого дочернего класса у вас есть другая таблица, каждая со свойствами, применимыми только к этому классу. Они связаны 1: 1 своими ПК.

альтернативный текст

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

альтернативный текст SessionTypeID - дискриминатор

Целевая иерархия быстрее запрашивается, поскольку вам не нужны объединения (только значение дискриминатора), тогда как целевой объект для каждой сущности вам нужно выполнять сложные объединения, чтобы определить тип чего-то, а также получить все его данные.

Изменить: изображения, которые я здесь показываю, являются снимками экрана проекта, над которым я работаю. Образ актива не завершен, отсюда его пустота, но в основном он был предназначен для того, чтобы показать, как его настроить, а не то, что поместить в ваши таблицы. Это зависит от вас;). Таблица сеансов содержит информацию о сеансах виртуального сотрудничества и может быть нескольких типов сеансов в зависимости от того, какой тип сотрудничества задействован.

mattlant
источник
Я также считаю, что класс Target per Concrete не очень хорошо моделирует наследование, поэтому я не показывал.
mattlant
Не могли бы вы добавить ссылку на иллюстрацию?
chryss
Где изображения, о которых вы говорите в конце своего ответа?
Муса Хайдари
1

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

Пер Хорншой-Ширбек
источник
2
почему люди считают, что нормализация базы данных снижает производительность? люди также думают, что принцип DRY снижает производительность кода? откуда взялось это заблуждение?
Стивен А. Лоу
1
Возможно, потому что денормализация может улучшить производительность, следовательно, нормализация, условно говоря, ухудшает ее. Не могу сказать, что согласен с этим, но, вероятно, так оно и произошло.
Мэтью Шарли,
2
Вначале нормализация может иметь небольшое влияние на производительность, но со временем, по мере увеличения количества строк, эффективные JOIN начнут превосходить более объемные таблицы. Конечно, у нормализации есть и другие, более весомые преимущества - последовательность, отсутствие избыточности и т. Д.
Роб
1

повторение аналогичного ответа на тему

в сопоставлении OR наследование сопоставляется с родительской таблицей, в которой родительская и дочерняя таблицы используют один и тот же идентификатор

например

create table Object (
    Id int NOT NULL --primary key, auto-increment
    Name varchar(32)
)
create table SubObject (
    Id int NOT NULL  --primary key and also foreign key to Object
    Description varchar(32)
)

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

РЕДАКТИРОВАТЬ: если вы также хотите моделировать поведение, вам понадобится таблица типов, в которой перечислены отношения наследования между таблицами и указаны имя сборки и класса, которые реализуют поведение каждой таблицы

кажется излишним, но все зависит от того, для чего вы хотите его использовать!

Стивен А. Лоу
источник
Это обсуждение закончилось тем, что мы добавили пару столбцов в каждую таблицу, а не моделировали наследование. Я считаю, что название этого обсуждения следует изменить, чтобы лучше отразить характер вопроса и обсуждения.
Even Mien
1

Используя SQL ALchemy (Python ORM), вы можете выполнять два типа наследования.

У меня был опыт использования одиночной таблицы и столбца дискриминанта. Например, база данных Sheep (без шуток!) Хранила всех Sheep в одной таблице, а Rams и Ewes обрабатывались с использованием столбца пола в этой таблице.

Таким образом, вы можете запросить всех овец и получить всех овец. Или вы можете запросить только Ram, и он получит только Rams. Вы также можете делать такие вещи, как иметь отношение, которое может быть только Бараном (то есть Отцом Овцы), и так далее.

Мэтью Шинкель
источник
1

Обратите внимание, что некоторые движки баз данных уже предоставляют механизмы наследования, такие как Postgres . Посмотрите документацию .

Например, вы можете запросить систему Person / Employee, описанную в ответе выше, следующим образом:

  / * Показывает имена всех людей или сотрудников * /
  ВЫБЕРИТЕ имя ОТ человека; 

  / * Показывает дату начала только для всех сотрудников * /
  ВЫБРАТЬ начальную дату ОТ сотрудника;

Это выбор вашей базы данных, вам не нужно быть особенно умным!

пьер
источник