Структура базы данных для хранения исторических данных - PullRequest
15 голосов
/ 20 августа 2010

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

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

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

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

Ответы [ 7 ]

10 голосов
/ 20 августа 2010

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

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



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

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

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

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

6 голосов
/ 20 августа 2010

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

Попробуйте такую ​​договоренность:

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.

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

4 голосов
/ 20 августа 2010

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

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

0 голосов
/ 21 августа 2010

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

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

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

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

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

0 голосов
/ 20 августа 2010

Наша система расчета заработной платы использует даты вступления в силу во многих таблицах.Таблица 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 голосов
/ 20 августа 2010

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

Wiley -The Data Warehouse Toolkit, Second Edition

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

0 голосов
/ 20 августа 2010

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

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

Так что мой будет выглядеть примерно так:дата, когда таблица клиентов была изменена (из значений в этой записи) на значения в более поздней записи значений в таблице CustomerTable

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...