Каковы лучшие практики моделирования наследования в базах данных?
Каковы компромиссы (например, возможность запросов)?
(Меня больше всего интересуют SQL Server и .NET, но я также хочу понять, как другие платформы решают эту проблему.)
Каковы лучшие практики моделирования наследования в базах данных?
Каковы компромиссы (например, возможность запросов)?
(Меня больше всего интересуют 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
Для любых строк, которые имеют тип 0 (человек), начальная дата всегда будет нулевой.
Стол на бетон (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) Сколько бронирований отелей у Джейн Доу на следующий год? (2) Сколько номеров забронировано на 10 апреля в отеле Seaview Inn?
В объектно-ориентированной системе запрос (1) является атрибутом объекта клиента, а запрос (2) является атрибутом объекта отеля. Это объекты, которые будут отображать эти свойства в своих API. (Хотя, очевидно, что внутренние механизмы, с помощью которых получаются эти значения, могут включать ссылки на другие объекты.)
В системе реляционной базы данных оба запроса будут проверять отношение резервирования, чтобы получить их номера, и концептуально нет необходимости возиться с какой-либо другой «сущностью».
Таким образом, правильная реляционная база данных создается путем попытки сохранить факты о мире, а не пытаться хранить сущности с атрибутами. И как только он будет правильно спроектирован, то полезные запросы, о которых нельзя было даже мечтать на этапе проектирования, можно легко построить, поскольку все факты, необходимые для выполнения этих запросов, находятся на своих местах.
Employment
таблица, в которой собраны все вакансии с датами их начала. Итак, если Employer
важно знать текущую дату начала работы для a , это может быть подходящим вариантом использования для a View
, который включает это свойство путем запроса? (примечание: кажется, из-за '-' сразу после моего ника я не получил никакого уведомления о вашем комментарии)
Короткий ответ: нет.
Если вам нужно сериализовать ваши объекты, используйте 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 - дискриминатор
Целевая иерархия быстрее запрашивается, поскольку вам не нужны объединения (только значение дискриминатора), тогда как целевой объект для каждой сущности вам нужно выполнять сложные объединения, чтобы определить тип чего-то, а также получить все его данные.
Изменить: изображения, которые я здесь показываю, являются снимками экрана проекта, над которым я работаю. Образ актива не завершен, отсюда его пустота, но в основном он был предназначен для того, чтобы показать, как его настроить, а не то, что поместить в ваши таблицы. Это зависит от вас;). Таблица сеансов содержит информацию о сеансах виртуального сотрудничества и может быть нескольких типов сеансов в зависимости от того, какой тип сотрудничества задействован.
Вы бы нормализовали свою базу данных, и это фактически отразило бы ваше наследование. У него может быть снижение производительности, но с нормализацией дело обстоит именно так. Вам, вероятно, придется руководствоваться здравым смыслом, чтобы найти баланс.
повторение аналогичного ответа на тему
в сопоставлении 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), вы можете выполнять два типа наследования.
У меня был опыт использования одиночной таблицы и столбца дискриминанта. Например, база данных Sheep (без шуток!) Хранила всех Sheep в одной таблице, а Rams и Ewes обрабатывались с использованием столбца пола в этой таблице.
Таким образом, вы можете запросить всех овец и получить всех овец. Или вы можете запросить только Ram, и он получит только Rams. Вы также можете делать такие вещи, как иметь отношение, которое может быть только Бараном (то есть Отцом Овцы), и так далее.
Обратите внимание, что некоторые движки баз данных уже предоставляют механизмы наследования, такие как Postgres . Посмотрите документацию .
Например, вы можете запросить систему Person / Employee, описанную в ответе выше, следующим образом:
/ * Показывает имена всех людей или сотрудников * / ВЫБЕРИТЕ имя ОТ человека; / * Показывает дату начала только для всех сотрудников * / ВЫБРАТЬ начальную дату ОТ сотрудника;
Это выбор вашей базы данных, вам не нужно быть особенно умным!