У нас есть требование в проекте хранить все ревизии (историю изменений) для объектов в базе данных. На данный момент у нас есть 2 разработанных предложения для этого:
например, для организации "Сотрудник"
Вариант 1:
-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"
-- Holds the Employee Revisions in Xml. The RevisionXML will contain
-- all data of that particular EmployeeId
"EmployeeHistories (EmployeeId, DateModified, RevisionXML)"
Вариант 2:
-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"
-- In this approach we have basically duplicated all the fields on Employees
-- in the EmployeeHistories and storing the revision data.
"EmployeeHistories (EmployeeId, RevisionId, DateModified, FirstName,
LastName, DepartmentId, .., ..)"
Есть ли другой способ сделать это?
Проблема с «Проектом 1» заключается в том, что нам приходится анализировать XML каждый раз, когда вам нужно получить доступ к данным. Это замедлит процесс, а также добавит некоторые ограничения, например, мы не можем добавлять объединения в поля данных ревизий.
И проблема с «Проектом 2» заключается в том, что мы должны дублировать каждое поле для всех сущностей (у нас есть около 70-80 сущностей, для которых мы хотим поддерживать исправления).
sql
database
database-design
versioning
Рамеш Сони
источник
источник
Ответы:
источник
SELECT * FROM EmployeeHistory WHERE LastName = 'Doe'
результаты в полном сканировании таблицы . Не лучшая идея масштабировать приложение.Я думаю, что ключевой вопрос, который следует здесь задать, - «Кто / что будет использовать историю»?
Если это будет в основном для отчетов / истории, удобочитаемой человеком, мы реализовали эту схему в прошлом ...
Создайте таблицу с именем AuditTrail или что-то в этом роде со следующими полями ...
Затем вы можете добавить столбец LastUpdatedByUserID во все свои таблицы, который должен устанавливаться каждый раз, когда вы выполняете обновление / вставку в таблицу.
Затем вы можете добавить триггер в каждую таблицу, чтобы перехватить любую вставку / обновление, которое происходит, и создать запись в этой таблице для каждого измененного поля. Поскольку таблица также снабжается LastUpdateByUserID для каждого обновления / вставки, вы можете получить доступ к этому значению в триггере и использовать его при добавлении в таблицу аудита.
Мы используем поле RecordID для хранения значения ключевого поля обновляемой таблицы. Если это комбинированный ключ, мы просто выполняем конкатенацию строк со знаком «~» между полями.
Я уверен, что у этой системы могут быть недостатки - для сильно обновленных баз данных может снизиться производительность, но для моего веб-приложения мы получаем намного больше операций чтения, чем записи, и, похоже, оно работает довольно хорошо. Мы даже написали небольшую утилиту VB.NET для автоматической записи триггеров на основе определений таблиц.
Просто мысль!
источник
sysname
может быть более подходящий тип данных для имен таблиц и столбцов.История Таблица статья в программатора базы данных блог может быть полезной - охватывают некоторые из вопросов , поднятых здесь и обсуждается хранение дельт.
редактировать
В эссе History Tables автор ( Кеннет Даунс ) рекомендует вести историческую таблицу как минимум из семи столбцов:
Столбцы, которые никогда не меняются или история которых не требуется, не должны отслеживаться в таблице истории, чтобы избежать раздувания. Сохранение дельты для числовых значений может упростить последующие запросы, даже если она может быть получена из старых и новых значений.
Таблица истории должна быть защищенной, чтобы пользователи, не являющиеся системными, не могли вставлять, обновлять или удалять строки. Следует поддерживать только периодическую очистку, чтобы уменьшить общий размер (и если это разрешено вариантом использования).
источник
Мы реализовали решение, очень похожее на решение, которое предлагает Крис Робертс, и оно нам очень подходит.
Единственная разница в том, что мы сохраняем только новое значение. Старое значение все-таки сохраняется в предыдущей строке истории
Допустим, у вас есть таблица с 20 столбцами. Таким образом, вам нужно сохранить только тот столбец, который был изменен, вместо того, чтобы хранить всю строку.
источник
Избегайте дизайна 1; это не очень удобно, если вам нужно, например, откатиться к старым версиям записей - автоматически или «вручную» с помощью консоли администратора.
Я не вижу недостатков в Design 2. Я думаю, что вторая таблица History должна содержать все столбцы, присутствующие в первой таблице Records. Например, в mysql вы можете легко создать таблицу с той же структурой, что и другая таблица (
create table X like Y
). И когда вы собираетесь изменить структуру таблицы Records в вашей живой базе данных, вамalter table
все равно придется использовать команды - и нет больших усилий для выполнения этих команд также для вашей таблицы History.Ноты
RevisionId
столбцом;ModifiedBy
- пользователь, создавший конкретную ревизию. Вы также можете захотеть иметь полеDeletedBy
для отслеживания того, кто удалил конкретную ревизию.DateModified
должно означать - либо это означает, где эта конкретная ревизия была создана, либо это будет означать, когда эта конкретная ревизия была заменена другой. Первый требует, чтобы поле находилось в таблице Records, и на первый взгляд кажется более интуитивным; однако второе решение кажется более практичным для удаленных записей (дата, когда была удалена эта конкретная ревизия). Если вы выберете первое решение, вам, вероятно, понадобится второе полеDateDeleted
(конечно, только если оно вам нужно). Зависит от вас и от того, что вы действительно хотите записать.Операции в Варианте 2 очень тривиальны:
ИзменитьЕсли вы выберете дизайн 2, все команды SQL, необходимые для этого, будут очень простыми, как и обслуживание! Возможно, будет намного проще использовать вспомогательные столбцы (
RevisionId
,DateModified
) также в таблице Records - чтобы обе таблицы имели одинаковую структуру (за исключением уникальных ключей)! Это позволит использовать простые команды SQL, которые будут устойчивы к любому изменению структуры данных:Не забывайте использовать транзакции!
Что касается масштабирования , это решение очень эффективно, поскольку вы не преобразуете какие-либо данные из XML туда и обратно, а просто копируете целые строки таблицы - очень простые запросы с использованием индексов - очень эффективно!
источник
Если вам нужно сохранить историю, создайте теневую таблицу с той же схемой, что и таблица, которую вы отслеживаете, и столбцами «Дата ревизии» и «Тип ревизии» (например, «удалить», «обновить»). Напишите (или сгенерируйте - см. Ниже) набор триггеров для заполнения таблицы аудита.
Довольно просто создать инструмент, который будет читать словарь системных данных для таблицы и генерировать скрипт, который создает теневую таблицу и набор триггеров для ее заполнения.
Не пытайтесь использовать для этого XML, хранилище XML намного менее эффективно, чем собственное хранилище таблиц базы данных, которое использует этот тип триггера.
источник
Рамеш, я участвовал в разработке системы на основе первого подхода.
Оказалось, что хранение изменений в формате XML приводит к огромному росту базы данных и значительно замедляет работу.
Мой подход состоял бы в том, чтобы иметь одну таблицу для каждой сущности:
где IsActive - признак последней версии
Если вы хотите связать дополнительную информацию с ревизиями, вы можете создать отдельную таблицу, содержащую эту информацию, и связать ее с таблицами сущностей, используя отношение PK \ FK.
Таким образом, вы можете хранить все версии сотрудников в одной таблице. Плюсы такого подхода:
Обратите внимание, что вы должны разрешить неуникальность первичного ключа.
источник
То, как я видел это в прошлом, было
Вы никогда не «обновляете» эту таблицу (за исключением того, чтобы изменить действительность isCurrent), просто вставляйте новые строки. Для любого заданного EmployeeId только 1 строка может иметь isCurrent == 1.
Сложность поддержания этого может быть скрыта представлениями и триггерами «вместо» (в Oracle я предполагаю аналогичные вещи в других СУБД), вы даже можете перейти к материализованным представлениям, если таблицы слишком велики и не могут обрабатываться индексами) ,
Этот метод подходит, но вы можете получить несколько сложных запросов.
Лично мне очень нравится ваш способ сделать это в Design 2, как я и делал это раньше. Его просто понять, легко реализовать и просто поддерживать.
Это также создает очень небольшие накладные расходы для базы данных и приложения, особенно при выполнении запросов чтения, что, вероятно, вы будете делать в 99% случаев.
Также было бы довольно легко автоматизировать создание таблиц истории и триггеров для обслуживания (при условии, что это будет сделано с помощью триггеров).
источник
Редакции данных - это аспект концепции « действительного времени » временной базы данных. Этому было посвящено много исследований, и появилось множество шаблонов и рекомендаций. Я написал длинный ответ с кучей ссылок на этот вопрос для заинтересованных.
источник
Я собираюсь поделиться с вами своим дизайном, и он отличается от ваших обоих дизайнов тем, что для каждого типа сущности требуется одна таблица. Я обнаружил, что лучший способ описать любой дизайн базы данных - через ERD, вот мой:
В этом примере у нас есть объект с именем employee . Таблица user содержит записи ваших пользователей, а entity и entity_revision - две таблицы, которые содержат историю изменений для всех типов сущностей, которые будут у вас в системе. Вот как работает этот дизайн:
Два поля entity_id и revision_id
Каждая сущность в вашей системе будет иметь собственный уникальный идентификатор сущности. Ваша сущность может претерпеть изменения, но ее entity_id останется прежним. Вам необходимо сохранить этот идентификатор объекта в таблице сотрудников (как внешний ключ). Вы также должны сохранить тип своей сущности в таблице сущностей (например, «сотрудник»). Теперь что касается revision_id, как видно из его названия, он отслеживает изменения вашей сущности. Лучший способ, который я нашел для этого, - использовать employee_id в качестве вашего revision_id. Это означает, что у вас будут одинаковые идентификаторы ревизий для разных типов сущностей, но для меня это не проблема (я не уверен насчет вашего случая). Единственное важное замечание: комбинация entity_id и revision_id должна быть уникальной.
Там также состояние поля в entity_revision таблице , которая указывает на состояние пересмотра. Он может иметь одно из трех состояний:
latest
,obsolete
илиdeleted
(не полагаясь на дату пересмотра поможет вам многое , чтобы повысить свои запросы).И последнее замечание о revision_id: я не создавал внешний ключ, соединяющий employee_id с revision_id, потому что мы не хотим изменять таблицу entity_revision для каждого типа сущности, который мы могли бы добавить в будущем.
INSERTION
Для каждого сотрудника , которого вы хотите вставить в базу данных, вы также добавите запись в entity и entity_revision . Эти последние две записи помогут вам отслеживать, кем и когда запись была вставлена в базу данных.
ОБНОВИТЬ
Каждое обновление для существующей записи о сотруднике будет реализовано как две вставки: одна в таблицу сотрудников и одна в entity_revision. Второй поможет узнать, кем и когда была обновлена запись.
УДАЛЕНИЕ
Для удаления сотрудника в entity_revision вставляется запись об удалении и завершение.
Как вы можете видеть в этом дизайне, данные никогда не изменяются или не удаляются из базы данных, и, что более важно, для каждого типа сущности требуется только одна таблица. Лично я считаю этот дизайн действительно гибким и простым в работе. Но я не уверен насчет вас, так как ваши потребности могут быть другими.
[ОБНОВИТЬ]
Имея поддержку разделов в новых версиях MySQL, я считаю, что мой дизайн также обладает одной из лучших характеристик. Можно разделить
entity
таблицу с помощьюtype
поля, а разделитьentity_revision
с помощью ееstate
поля. Это значительно увеличит количествоSELECT
запросов, сохраняя при этом простой и понятный дизайн.источник
Если вам действительно нужен журнал аудита, я бы склонился к решению с таблицей аудита (в комплекте с денормализованными копиями важных столбцов в других таблицах, например,
UserName
). Однако имейте в виду, что этот горький опыт показывает, что одна таблица аудита будет огромным узким местом в будущем; вероятно, стоит потратить усилия на создание отдельных таблиц аудита для всех ваших проверенных таблиц.Если вам нужно отслеживать фактические исторические (и / или будущие) версии, то стандартным решением является отслеживание одного и того же объекта с несколькими строками с использованием некоторой комбинации значений начала, конца и продолжительности. Вы можете использовать представление, чтобы упростить доступ к текущим значениям. Если вы придерживаетесь этого подхода, вы можете столкнуться с проблемами, если ваши версионные данные будут ссылаться на изменяемые, но неверсированные данные.
источник
Если вы хотите сделать первый, вы можете также использовать XML для таблицы «Сотрудники». Большинство новых баз данных позволяют выполнять запросы к полям XML, поэтому это не всегда проблема. И может быть проще иметь один способ доступа к данным сотрудников, независимо от того, последняя это версия или более ранняя.
Хотя я бы попробовал второй подход. Вы можете упростить это, имея только одну таблицу сотрудников с полем DateModified. EmployeeId + DateModified будет первичным ключом, и вы можете сохранить новую версию, просто добавив строку. Таким образом, архивировать старые версии и восстанавливать версии из архива также проще.
Другой способ сделать это - модель хранилища данных Дэна Линстедта. Я сделал проект для голландского статистического бюро, в котором использовалась эта модель, и она работает достаточно хорошо. Но я не думаю, что это полезно для повседневного использования баз данных. Хотя вы можете почерпнуть некоторые идеи из его статей.
источник
Как насчет:
Вы делаете первичный ключ (EmployeeId, DateModified), а чтобы получить «текущую» запись (записи), вы просто выбираете MAX (DateModified) для каждого идентификатора сотрудника. Хранение IsCurrent - очень плохая идея, потому что, во-первых, его можно вычислить, а во-вторых, данные слишком легко рассинхронизируются.
Вы также можете создать представление, в котором перечислены только последние записи, и в основном использовать его при работе в приложении. Хорошая вещь в этом подходе заключается в том, что у вас нет дубликатов данных, и вам не нужно собирать данные из двух разных мест (текущих в Employees и заархивированных в EmployeesHistory), чтобы получить всю историю или откат и т. Д.) ,
источник
Если вы хотите полагаться на данные истории (для отчетов), вы должны использовать такую структуру:
Или глобальное решение для применения:
Вы можете сохранить свои ревизии также в XML, тогда у вас будет только одна запись для одной ревизии. Это будет выглядеть так:
источник
У нас были похожие требования, и мы обнаружили, что зачастую пользователь просто хочет увидеть что было изменено, не обязательно откатить любые изменения.
Я не уверен, каков ваш вариант использования, но мы создали и создали таблицу аудита, которая автоматически обновляется с изменениями бизнес-объекта, включая понятное имя любых ссылок и перечислений внешнего ключа.
Всякий раз, когда пользователь сохраняет свои изменения, мы перезагружаем старый объект, запускаем сравнение, записываем изменения и сохраняем объект (все это делается в одной транзакции базы данных на случай возникновения проблем).
Это, кажется, очень хорошо работает для наших пользователей и избавляет нас от головной боли, связанной с наличием полностью отдельной таблицы аудита с теми же полями, что и наша бизнес-сущность.
источник
Похоже, вы хотите отслеживать изменения определенных объектов с течением времени, например, ID 3, «bob», «123 main street», затем еще ID 3, «bob», «234 elm st» и т. Д., По сути, имея возможность чтобы вырвать историю изменений, показывающую каждый адрес, по которому был "bob".
Лучший способ сделать это - иметь поле «текущее» в каждой записи и (возможно) временную метку или FK в таблице даты / времени.
Затем вставки должны установить «является текущим», а также сбросить «текущее значение» для предыдущей «текущей» записи. В запросах должно быть указано «текущее», если вам не нужна вся история.
Есть и другие настройки, если это очень большая таблица или ожидается большое количество изменений, но это довольно стандартный подход.
источник