Как эффективно моделировать наследование в базе данных?
Каковы лучшие практики для моделирования наследования в базах данных?
Каковы компромиссы (например, вопрососпособность)?
(Меня больше всего интересуют SQL Server и.NET, но я также хочу понять, как другие платформы решают эту проблему.)
9 ответов
Существует несколько способов моделирования наследования в базе данных. Что вы выберете, зависит от ваших потребностей. Вот несколько вариантов:
Таблица по типу (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 (Person), начальная дата всегда будет нулевой.
Стол на бетон (TPC)
Каждый класс имеет свою собственную полностью сформированную таблицу без ссылок на любые другие таблицы.
Учитывая приведенные выше классы, вы получите следующие таблицы:
table Person
------------
int id (PK)
string firstname
string lastname
table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate
Правильный дизайн базы данных не похож на правильный дизайн объекта.
Если вы планируете использовать базу данных для чего-то другого, кроме простой сериализации ваших объектов (например, отчетов, запросов, многозадачного использования, бизнес-аналитики и т. Д.), То я не рекомендую никакого простого сопоставления объектов с таблицами.
Многие люди думают о строке в таблице базы данных как о сущности (я много лет думал об этом), но строка не является сущностью. Это предложение. Отношение к базе данных (т. Е. Таблица) представляет собой некое утверждение о мире. Наличие строки указывает, что факт является истинным (и наоборот, его отсутствие указывает, что факт является ложным).
С этим пониманием вы можете видеть, что один тип в объектно-ориентированной программе может храниться в дюжине различных отношений. А различные типы (объединенные наследованием, ассоциацией, агрегацией или совершенно неаффилированные) могут частично храниться в одном отношении.
Лучше всего спросить себя, какие факты вы хотите хранить, на какие вопросы вы хотите получить ответы, какие отчеты вы хотите генерировать.
После создания правильного дизайна БД очень просто создать запросы / представления, которые позволят вам сериализовать ваши объекты в эти отношения.
Пример:
В системе бронирования отелей вам может понадобиться сохранить тот факт, что Джейн Доу забронировала номер в отеле Seaview Inn на 10-12 апреля. Это атрибут объекта клиента? Это атрибут отеля? Это объект бронирования с объектами, включающими клиента и отель? Это может быть любая или все эти вещи в объектно-ориентированной системе. В базе данных это не так. Это просто голый факт.
Чтобы увидеть разницу, рассмотрим следующие два запроса. (1) Сколько отелей Jane Doe бронирует на следующий год? (2) Сколько номеров забронировано на 10 апреля в отеле Seaview Inn?
В объектно-ориентированной системе запрос (1) является атрибутом объекта клиента, а запрос (2) является атрибутом объекта отеля. Это объекты, которые выставляют эти свойства в своих API. (Хотя очевидно, что внутренние механизмы, с помощью которых эти значения получаются, могут включать ссылки на другие объекты.)
В системе реляционной базы данных оба запроса проверяют отношение резервирования, чтобы получить их числа, и концептуально нет необходимости беспокоиться о какой-либо другой "сущности".
Таким образом, именно пытаясь хранить факты о мире, а не пытаться хранить сущности с атрибутами, создается надлежащая реляционная база данных. И как только он будет должным образом спроектирован, тогда можно будет легко построить полезные запросы, о которых не мечтали на этапе проектирования, поскольку все факты, необходимые для выполнения этих запросов, находятся на своих местах.
Как сказал Брэд Уилсон, паттерны 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;
Краткий ответ: нет.
Если вам нужно сериализовать ваши объекты, используйте ORM или, что еще лучше, что-то вроде activerecord или prevaylence.
Если вам нужно хранить данные, храните их в реляционной манере (будьте осторожны с тем, что вы храните и обращаете внимание на то, что только что сказал Джеффри Л. Уитледж), а не на тот, на который влияет ваш объектный дизайн.
Существует два основных типа наследования, которые вы можете настроить в БД: таблица для сущности и таблица для иерархии.
Таблица для каждой сущности - это таблица базовых сущностей, которая имеет общие свойства всех дочерних классов. Затем у каждого дочернего класса есть другая таблица, каждая из которых имеет только свойства, применимые к этому классу. Они связаны 1:1 их ПК
Таблица в иерархии - это место, где все классы совместно используют таблицу, а необязательные свойства имеют значение NULL. Их также является полем дискриминатора, который представляет собой число, обозначающее тип, который в данный момент хранится в записи.
SessionTypeID является дискриминатором
Цели на иерархию быстрее запрашивать, так как вам не нужны объединения (только значение дискриминатора), в то время как цели на сущность вам нужно делать сложные объединения, чтобы определить, к какому типу относится что-либо, а также получить все его данные.
Изменить: изображения, которые я показываю здесь, являются скриншотами проекта, над которым я работаю. Образ актива не является полным, отсюда и его пустота, но в основном он должен был показать, как его настройка, а не то, что поместить в ваши таблицы. Это зависит от вас;). Сеансовая таблица содержит информацию о сеансе виртуальной совместной работы и может быть нескольких типов сессий в зависимости от типа совместной работы.
Обратите внимание, что некоторые движки баз данных уже предоставляют такие механизмы наследования, как Postgres. Посмотри документацию.
Например, вы бы запросили систему Person / Employee, описанную в ответе выше, например:
/ * Это показывает имя всех людей или сотрудников * / ВЫБЕРИТЕ имя от персоны; / * Здесь показана только дата начала работы всех сотрудников * / ВЫБЕРИТЕ начальную дату ОТ сотрудника;
В этом заключается выбор вашей базы данных, вам не нужно быть особенно умным!
в сопоставлении 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 в обеих строках
РЕДАКТИРОВАТЬ: если вы хотите также моделировать поведение, вам понадобится таблица типов, в которой перечислены отношения наследования между таблицами и указаны имя сборки и класса, которые реализуют поведение каждой таблицы.
кажется излишним, но все зависит от того, для чего вы хотите его использовать!
Используя SQL ALchemy (Python ORM), вы можете сделать два типа наследования.
У меня был опыт использования единого стола и наличия дискриминанта. Например, база данных овец (без шуток!) Хранила всех овец в одной таблице, а баранов и овец обрабатывали с использованием столбца пола в этой таблице.
Таким образом, вы можете запросить все овцы и получить все овцы. Или вы можете запросить только Рам, и он будет получать только баранов. Вы также можете делать такие вещи, как иметь отношение, которое может быть только Бараном (т. Е. Сыном Овцы) и так далее.
Вы бы нормализовали свою базу данных, и это фактически отразило бы ваше наследование. Это может привести к снижению производительности, но так происходит с нормализацией. Вам, вероятно, придется использовать здравый смысл, чтобы найти баланс.