Ограничения на битемпоральную базу данных
Фон:
Я проектирую битемпоральную базу данных, где у нас есть отношения 1:N между битемпоральными таблицами (у нас также есть отношения M:N, но они просто смоделированы с помощью таблицы соединителей и двух отношений 1:N, поэтому я считаю их особенными случай отношения 1:N).
Чтобы проиллюстрировать это, давайте рассмотрим простой случай с двумя таблицами:
|===============| |==================|
| tblOrder | | tblOrderItem |
|============== | |==================|
| - OrderId | | - OrderItemId |
| - OrderNumber | | - FK_OrderId |
|===============| | - Amount |
|==================|
FK_OrderId
это внешний ключ к tblOrder
,
Чтобы сделать эту модель базы данных битемпоральной, я придумал следующий дизайн:
|===============| |==================| |====================|
| tblOrder | | tblOrderItem | | tblVersions |
|============== | |==================| |====================|
| - Id | | - Id | | - VersionId |
| - OrderId | | - OrderItemId | | - VersionDate |
| - OrderNumber | | - FK_OrderId | |====================|
| - VersionId | | - Amount |
| - IsDeleted | | - VersionId |
| - StartDate | | - IsDeleted |
| - EndDate | | - StartDate |
|===============| | - EndDate |
|==================|
Пояснения:
-
VersionId
столбцы являются внешними ключами кtblVersions
Таблица. Для каждого изменения в базе данных, запись вtblVersions
таблица создана. Текущее состояние данных - это просто сумма всех версий. Это позволяет восстановить предыдущие состояния базы данных (черезWHERE VersionDate < ...
пункт). Это измерение времени транзакции битемпоральности. -
tblVersions table could also be avoided if we're just including the
VersionDate` в две таблицы данных. -
StartDate
а такжеEndDate
столбцы - действительная временная размерность битемпоральности. Да,EndDate
является излишним, мы могли бы моделировать таблицы только сStartTime
, -
Id
столбцы двух таблиц являются новыми первичными ключами. Поскольку у нас есть несколько строк для одного и того же объекта (несколько версий, несколько диапазонов дат в действительное время), идентификатор объекта не может быть первичным ключом таблицы. КолонныOrderId
а такжеOrderItemId
являются идентификаторами сущности, но не первичным ключом таблицы. Вместо создания новых первичных ключейId
мы также могли бы определить первичный ключ как(OrderId, VersionId, StartDate)
, - Если объект удаляется, мы просто создаем новую запись версии, а также запись в таблице сущностей с
IsDeleted = 1
, Все остальные записи в таблице (вставки и обновления) имеютIsDeleted = 0
, - Колонка
FK_OrderId
изtblOrderitem
ссылается на столбецOrderId
изtblOrder
, Это уже не настоящий внешний ключ (в смысле ограничения базы данных), так какOrderId
больше не первичный ключ. Но это все еще говорит нам, какие OrderItems являются частью определенного Order.
Кажется, это работает хорошо, мы создали необходимые CRUD-запросы и можем читать и записывать битемпоральные данные.
Вопрос:
Какие ограничения мне нужны, чтобы это работало последовательно?
Меня не интересует, как реализовать ограничения (следует ли реализовывать их как ограничения базы данных, такие как FOREIGN KEY
с или UNIQUE
ограничения, или TRIGGER
с или CHECK
с, что угодно). Мне просто нужно знать, какие типы ограничений мне нужны.
Я выяснил кучу ограничений, которые я опубликую в качестве ответа. Но может быть, есть еще?
1 ответ
(Я использую аббревиатуру PIVT = "точка в действительном времени". Это обозначает определенный момент времени в действительном измерении времени)
Вот ограничения, о которых я уже подумал:
FK_VersionId
: Очевидно, нам нужны стандартные ограничения внешнего ключа наVersionId
колонны.- Битемпоральная уникальность: также сочетание
(OrderId, VersionId, StartDate)
должен быть уникальным (и одинаковым дляtblOrderItem
). - Действительная сериализация времени: нам нужно проверить, что для каждой сущности и каждой версии не существует перекрытий в действительном времени, то есть столбцах
StartDate
а такжеEndDate
не перекрываются иStartDate
всегда раньше, чемEndDate
, - Целостность удаления. Нам необходимо убедиться, что для каждого объекта и каждого PIVT существует не более одной строки с
IsDeleted = 1
и, если есть такая строка, после этой строки не должно быть никакой версии объекта. - Ссылочная целостность: нам нужно проверить, что для каждого объекта OrderItem, каждой версии и каждого PIVT значение
FK_OrderId
устанавливается в значение, которое идентифицирует сущность Order, которая существует в данном PIVT, которая была вставлена в более раннюю версию и которая не была удалена (путем установкиIsDeleted = 1
).