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