Как эффективно моделировать наследование в базе данных? - PullRequest
115 голосов
/ 10 октября 2008

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

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

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

Ответы [ 9 ]

136 голосов
/ 10 октября 2008

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

Таблица по типу (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
116 голосов
/ 11 октября 2008

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

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

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

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

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

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

Пример:

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

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

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

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

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

8 голосов
/ 11 октября 2008

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

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

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

7 голосов
/ 22 августа 2014

Шаблоны 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;
4 голосов
/ 10 октября 2008

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

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

alt text

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

alt text SessionTypeID является дискриминатором

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

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

1 голос
/ 10 октября 2008

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

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

  /* This shows the first name of all persons or employees */
  SELECT firstname FROM Person ; 

  /* This shows the start date of all employees only */
  SELECT startdate FROM Employee ;

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

1 голос
/ 10 октября 2008

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

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

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

1 голос
/ 10 октября 2008

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

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

например

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 в обеих строках

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

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

1 голос
/ 10 октября 2008

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

...