Структура базы данных для хранения исторических данных

Предисловие: На днях я думал о новой структуре базы данных для нового приложения и понял, что нам нужен способ эффективного хранения исторических данных. Я хотел, чтобы кто-то еще посмотрел и посмотрел, есть ли проблемы с этой структурой. Я понимаю, что этот метод хранения данных вполне мог быть изобретен ранее (я почти уверен, что он есть), но я понятия не имею, есть ли у него имя, и некоторые поиски в Google, которые я пробовал, ничего не дали.

Проблема : Допустим, у вас есть таблица заказов, а заказы связаны с таблицей клиентов для клиента, разместившего заказ. В обычной структуре базы данных вы можете ожидать что-то вроде этого:

orders
------
orderID
customerID


customers
---------
customerID
address
address2
city
state
zip

Довольно просто, orderID имеет внешний ключ customerID, который является первичным ключом таблицы customer. Но если мы собираемся запустить отчет по таблице заказов, мы собираемся присоединить таблицу клиентов к таблице заказов, которая вернет текущую запись для этого идентификатора клиента. Что если при размещении заказа адрес клиента был другим, и он впоследствии был изменен. Теперь наш заказ больше не отражает историю адресов этих клиентов на момент размещения заказа. По сути, изменив запись клиента, мы просто изменили всю историю этого клиента.

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

Что, если я сделал структуру, подобную это вместо этого:

orders
------
orderID
customerID
customerHistoryID


customers
---------
customerID
customerHistoryID


customerHistory
--------
customerHistoryID
customerID
address
address2
city
state
zip
updatedBy
updatedOn

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

Добавив столбец updatedby и updatedon в таблицу customerHistory, вы также можете увидеть «журнал аудита» данных, чтобы вы могли видеть, кто и когда внес изменения.

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

Я думаю, что все таблицы будут использовать эту структуру. Каждый раз, когда извлекаются исторические данные, они объединяются с таблицей истории, используя customerHistoryID, чтобы показать состояние данных для этого конкретного заказа.

Извлечь список клиентов легко, достаточно просто присоединиться к таблице клиентов по customerHistoryID.

Может кто-нибудь увидеть какие-либо проблемы с этим подходом, либо с точки зрения дизайна, либо с точки зрения производительности, почему это плохо. Помните, что независимо от того, что я делаю, мне нужно убедиться, что исторические данные сохраняются, чтобы последующие обновления записей не меняли историю. Есть ли способ лучше? Это известная идея, у которой есть имя или какая-либо документация?

Спасибо за любую помощь.

Обновление: Это очень простой пример того, что я действительно собираюсь получить. Мое реальное приложение будет иметь «заказы» с несколькими внешними ключами к другим таблицам. Информация о месте отправления / назначения, информация о клиенте, информация об объекте, информация о пользователе и т. Д. Несколько раз мне предлагалось скопировать информацию в запись заказа в тот момент, и я видел, как это делалось много раз, но это приведет к записи с сотнями столбцов, что на самом деле невозможно в этом случае.

16
задан Adriaan Koster 27 February 2015 в 12:31
поделиться

7 ответов

Когда я сталкивался с такими проблемами, можно было сделать заказ в таблице истории.Его функции такие же, но за ним немного легче следить

orders
------
orderID
customerID
address
City
state
zip



customers
---------
customerID
address
City
state
zip

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

Если вы выберете другой вариант и используете таблицы истории, вам следует рассмотреть возможность использования битемпоральных данных, поскольку вам, возможно, придется иметь дело с возможностью корректировки исторических данных. Например, клиент изменил свой текущий адрес с A на B, но вы также должны исправить адрес в существующем заказе, который в настоящее время выполняется.

Также, если вы используете MS SQL Server, вы можете рассмотреть возможность использования индексированных представлений. Это позволит вам обменивать небольшое инкрементное уменьшение производительности вставки / обновления на большое увеличение производительности выбора. Если вы не используете сервер MS SQL, вы можете воспроизвести это с помощью триггеров и таблиц.

10
ответ дан 30 November 2019 в 22:01
поделиться

Наша система расчета заработной платы использует даты вступления в силу во многих таблицах. Таблица ADDRESSES привязана к EMPLID и EFFDT. Это позволяет нам отслеживать каждый раз, когда меняется адрес сотрудника. Вы можете использовать ту же логику для отслеживания исторических адресов клиентов. Ваши запросы просто должны включать предложение, которое сравнивает дату заказа с датой адреса клиента, которая действовала на момент заказа. Например,

select o.orderID, c.customerID, c.address, c.city, c.state, c.zip
from orders o, customers c
where c.customerID = o.customerID
and c.effdt = (
   select max(c1.effdt) from customers c1
   where c1.customerID = c.customerID and c1.effdt <= o.orderdt
)

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

0
ответ дан 30 November 2019 в 22:01
поделиться

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

Wiley -The Data Warehouse Toolkit, Second Edition

Это хорошее начало.

0
ответ дан 30 November 2019 в 22:01
поделиться

Когда вы проектируете свои структуры данных, будьте очень осторожны, сохраняя правильные отношения, а не то, что похоже на правильные отношения. Если адрес заказа необходимо сохранить, то это потому, что адрес является частью заказа, а не клиентом. Кроме того, цены за единицу продукции являются частью заказа, а не продукта и т. Д.

Попробуйте такую ​​схему:

Customer
--------
CustomerId (PK)
Name
AddressId (FK)
PhoneNumber
Email

Order
-----
OrderId (PK)
CustomerId (FK)
ShippingAddressId (FK)
BillingAddressId (FK)
TotalAmount

Address
-------
AddressId (PK)
AddressLine1
AddressLine2
City
Region
Country
PostalCode

OrderLineItem
-------------
OrderId (PK) (FK)
OrderItemSequence (PK)
ProductId (FK)
UnitPrice
Quantity

Product
-------
ProductId (PK)
Price

etc.

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

6
ответ дан 30 November 2019 в 22:01
поделиться

Если вы искренне интересуетесь такими проблемами, я могу только предложить вам серьезно взглянуть на «Временные данные и реляционная модель».

Предупреждение1: здесь нет SQL, и почти все, что вы думаете о реляционной модели, будет заявлено как ложь. По уважительной причине.

Предупреждение2: от вас ожидается, что вы будете думать и много думать.

Предупреждение3: в книге говорится о том, как должно выглядеть решение для этого конкретного семейства проблем, но, как сказано во введении, она не о какой-либо технологии, доступной сегодня.

Тем не менее, книга является подлинным просветлением. По крайней мере, это помогает прояснить, что решение таких проблем не будет найдено ни в SQl в его нынешнем виде, ни в ORM, если на то пошло.

2
ответ дан 30 November 2019 в 22:01
поделиться

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

В вашей таблице заказов не должно быть сотен столбцов. У вас будет таблица заказов и подробная таблица заказов из-за отношений "один-ко-многим". Таблица заказов будет включать номер заказа. идентификатор клиента 9, чтобы вы могли искать все, что этот клиент когда-либо заказывал, даже если имя изменилось), имя клиента, адрес клиента (обратите внимание, что вам не нужен почтовый индекс города и т. д., введите адрес в одно поле), дату заказа и, возможно, несколько других полей, которые относятся непосредственно к заказу на верхнем уровне. Затем у вас есть таблица деталей заказа, в которой есть номер заказа, detail_id, номер детали, описание детали (это может быть объединение нескольких полей, таких как размер, цвет и т. Д., Или вы можете выделить наиболее распространенные), количество элементов, тип единицы, цена за единицу, налоги, общая цена, дата отгрузки, статус. Вы вводите одну запись для каждого заказанного товара.

4
ответ дан 30 November 2019 в 22:01
поделиться

Я предпочитаю, чтобы все было просто. Я бы использовал две таблицы, таблицу клиентов и таблицу истории клиентов. Если у вас есть ключ (например, customerId) в таблице истории, нет причин создавать объединяющуюся таблицу, выбор этого ключа предоставит вам все записи.

У вас также нет контрольной информации (например, даты изменения, кто изменил и т.д.) в таблице истории, как вы ее показываете, я думаю, вам это нужно.

Итак, мое будет выглядеть примерно так:

CustomerTable  (this contains current customer information)
CustID (distinct non null)
...all customer information fields

CustomerHistoryTable
CustId (not distinct non null)
...all customer information fields
DateOfChange 
WhoChanged

Поле DataOfChagne - это дата изменения таблицы клиентов (со значений в этой записи) на значения в более поздней записи значений в таблице клиентов

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

0
ответ дан 30 November 2019 в 22:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: