2014-01-27 3 views
3

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

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

VersionTime datetime -- Time when this versions becomes effective 
IsCurrent bool -- Indicates whether this version is the most current (and not deleted) 

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

Более общий вариант заключается в следующем: Версии записей могут быть добавлены в любое время для любого заданного диапазона времени действия. Поэтому я могу заявить, что некоторые настройки объекта действительны до конца 2013 года, а другая версия действует в 2014 году, а другая версия будет действовать с 2015 года. Это можно использовать как для архивирования старых данных (как указано выше), так и для планирования вперед для использования разных данных в будущем (и для сохранения этой информации в виде архива). Для этого, я хотел бы добавить следующие столбцы каждой таблицы:

ValidFrom datetime -- Time when this version becomes valid (inclusive) 
ValidTo datetime -- Time when this version becomes invalid (exclusive) 

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

Теперь мне интересно, как реализовать эффективное решение для управления и запроса таких данных. Обычно вы можете просто писать любые SQL-запросы с любыми типами WHERE, GROUP BY и JOIN, чтобы получить нужные вам записи. Но при применении версий вы должны рассмотреть правильную версию каждой записи. Поэтому вместо того, чтобы присоединяться к каждой версии записи из другой таблицы, необходимо добавить соответствующее условие, чтобы выбрать только версию, действительную в данный момент.

Пример:

SELECT a, b, c 
FROM t1 

Должно быть изменено на:

SELECT a, b, c 
FROM t1 
WHERE t1.ValidFrom <= :time AND t1.ValidTo > :time 
ORDER BY t1.ValidFrom 
LIMIT 1 

Сложнее с таблицей присоединения:

SELECT a, b, c 
FROM t1 
    LEFT JOIN t2 ON (t2.a = t1.a) 

Должно быть изменено на:

SELECT a, b, c 
FROM t1 
    LEFT JOIN t2 ON (t2.a = t1.a) 
WHERE t1.ValidFrom <= :time AND t1.ValidTo > :time 
    AND t2.ValidFrom <= :time AND t2.ValidTo > :time 

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

Я хочу создать класс (в C# в моем случае), который предоставляет методы для чтения и записи таких версий. Запись довольно проста, потому что запросы просты и легко контролируются транзакциями. но для запросов потребуется создание API, который принимает каждый фрагмент SQL SELECT-запроса и интеллектуально строит SQL-запрос для его выполнения. Метод thie query должен принимать только один дополнительный параметр, определяющий время для извлечения данных. В зависимости от диапазона действительности каждого объекта будут выбраны разные версии каждого из них.

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

+0

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

+0

Насколько я понимаю вас правильно: вы имеете в виду дублирование таблицы и сохранение самых недавно записанных данных в одной и всех предыдущих версиях (с датой) в другой таблице? Это будет охватывать только первый, более простой сценарий, а не второй. Кроме того, он ничего не говорит о выполнении запроса «точно в срок». – ygoe

ответ

1

Я работаю с SQL в продуктах Oracle (Database 11g). У нас огромный проект, и управление версиями - неотъемлемая часть его. Оба подхода, о которых вы говорили, полезны.
Если триггеры поддержки базы данных и вы можете использовать PL/SQL, вы можете отделить старые данные с небольшой долей усилий. Вы можете создать before update и before delete триггеры, а затем сохранить все старые данные внутри специальной исторической таблицы (с датой изменения и типа - удаление или обновление)

Допущение: Все таблицы, которые вы хотите версионности должны иметь первичный ключ.

псевдокод:

CREATE TRIGGER TRIGGER_ON_VERSIONED_TABLE 
BEFORE UPDATE 
    ON VERSIONED_TABLE 
BEGIN 
    INSERT INTO VERSIONED_TABLE_HISTORY_PART VALUES (:OLD.COLUMN_A, USER, TIMESTAMP); 
END 

Если вы хотите, чтобы все исторические данные об одном первичном ключе, вы можете выбрать данные из «производства» таблицы и исторической таблицы выберите только ключ вы хотите и сортировать по дате (для активной записи будет timestamp SYSTIMESTAMP). И если вы хотите увидеть, в каком состоянии находится запись, вы можете выбрать первую строку, для которой ваша дата больше, чем дата в истории (или производственная таблица).

For before update trigger look here.

Если существующее решение
(так, исходная модель БД не содержит управления версиями частей)
и вы хотите создать версионную таблицу, или вы не можете использовать PL/SQL, используйте свой подход 2. Наш проект на работе (в Oracle Database) также использует этот подход. Пусть у нас есть таблица с документами (в реальной жизни вы у вас есть идентификатор версии, который будет первичным ключом для этой таблицы, но это только, чтобы показать принципы)

CREATE TABLE DOC(
    DOC_NAME VARCHAR(10) 
    , DOC_NOTE VARCHAR(10) 
    , VALID_FROM TIMESTAMP 
    , VALID_TO TIMESTAMP 
    , CONSTRAINT DOC_PK PRIMARY KEY(DOCUMENT_NAME, VALID_FROM) 
); 

INSERT INTO doc VALUES ('A', 'FIRST VER', systimestamp, date'2999-12-31'); 
INSERT INTO doc VALUES ('B', 'FIRST VER', systimestamp, date'2999-12-31'); 

Вам не нужно, где, как это:

WHERE VALID_FROM <= :time AND VALID_TO > :time 
ORDER BY VALID_FROM LIMIT 1 

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

SELECT * FROM DOC 
WHERE SYSTIMESTAMP BETWEEN VALID_FROM AND VALID_TO; 

Это всегда возвращает вам только одну строку и вы можете использовать вместо SYSTIMESTAMP любую другую дату. Но вы не можете обновлять записи напрямую, во-первых, вы должны обновить конечную метку времени (но это не проблема для вас, как я вижу). Так что, если я обновлю XK-04, я делаю это так:

UPDATE doc SET VALID_TO = systimestamp 
WHERE DOC_NAME='A' AND SYSTIMESTAMP BETWEEN VALID_FROM AND VALID_TO; 
INSERT INTO doc VALUES ('A', 'SECOND VER', systimestamp, date'2999-12-31'); 

И вы можете использовать те же выбрать, как описано выше снова.

SELECT * FROM DOC WHERE :CUSTOM_DATE BETWEEN VALID_FROM AND VALID_TO; 

Лучшая практика - создать для версии таблицы также АКТИВНЫЙ и ИСТОРИЧЕСКИЙ виды. В базовом столе у ​​вас есть все данные, и в любое время, когда вы хотите получить реальную запись, вы должны написать BETWEEN VALID_FROM AND VALID_TO.Лучший способ это создать представление:

CREATE VIEW DOC_ACTIVE 
AS SELECT * FROM DOC WHERE SYSTIMESTAMP BETWEEN VALID_FROM AND VALID_TO; 

Или, если вам нужно для старых данных:

CREATE VIEW DOC_INACTIVE 
AS SELECT * FROM DOC WHERE NOT SYSTIMESTAMP BETWEEN VALID_FROM AND VALID_TO; 

Теперь вместо оригинального SQL:

SELECT a, b, c FROM t1 

вам не нужно использовать сложную структуру , только изменить таблицу на «активный» вид (например, DOC_ACTIVE):

SELECT a, b, c FROM t1_VIEW 

Пожалуйста, посмотрите и на этот ответ: Versioning in SQL Tables - how to handle it?

Я не знаю, видите ли вы видимую разницу между действительной записью и действительным «объектом» внутри. В нашем рабочем проекте у нас нет допустимых перекрывающихся диапазонов. Например, указанная таблица с документами, составной составной ключ из названия документа и номера версии ... У нас есть документ A (и этот документ действителен в течение лет 2010 - 2050) он имеет 2 версии.

Document A, version 1 (2010-2020), record valid 2014-9999: VALID (NEW) 
Document A, version 2 (2021-2050), record valid 2014-9999: VALID (NEW) 

В версии 1 является документом действует с 2010 до 2020 года (объект версии, а не запись версии) документа в некотором состоянии P. Эта запись действует с 2014-9999.

В версии 2 документ действителен с 2021 по 2050 год (версия объекта, а не версия записи) Эта запись действительна снова между 2014-9999 годом. И документ находится в состоянии Q.

Предположим, что это 2016. Вы нашли ошибку в обеих версиях документа. Вы создаете для текущей версии (2016) новую версию для обеих версий документа. После всех изменений, у вас есть это версия документа:

Document A, version 1 (2010-2020), record valid 2014-2015: INVALID (UPDATED) 
Document A, version 2 (2021-2050), record valid 2014-2015: INVALID (UPDATED) 
Document A, version 1 (2010-2020), record valid 2016-9999: VALID NOW (NEW) 
Document A, version 2 (2021-2050), record valid 2016-9999: VALID NOW (NEW) 

После этого, в 2018 году, кто-то создать новую версию документа, действительно только в течение многих лет 2021-2030. (Документ действителен в будущем, но его версия действительна сегодня) Теперь вы должны обновить ДЕЙСТВИТЕЛЬНО версии 2 и создавать версии 3. Фактическое состояние:

Document A, version 1 (2010-2020), record valid 2014-2015: INVALID (NO CHANGE) 
Document A, version 2 (2021-2050), record valid 2014-2015: INVALID (NO CHANGE) 
Document A, version 1 (2010-2020), record valid 2016-9999: VALID NOW (NO CHANGE) 
Document A, version 2 (2021-2050), record valid 2016-2018: INVALID (UPDATED) 
Document A, version 2 (2031-2050), record valid 2018-9999: VALID NOW (NEW) 
Document A, version 3 (2021-2030), record valid 2018-9999: VALID NOW (NEW) 

Все эти операции для нас в нашей работе проекта сделать PL/SQL-код.
В 2018 году, если вы выберете документ для действительных записей, вы получите 3 строки: A1 A2 A3.
Если вы выберете версии, действующие в 2015 году, вы получите только A1 (INVALID) A2 (INVALID).

Итак, у вас есть полная история, даже если документ имеет 3 действительную версию, действительную в той же точке (достоверность записи). И действительность объекта разделяется. Это действительно хороший подход и должен охватывать все ваши требования.

Вы можете легко использовать BETWEEN в ВИДЕ также для столбцов с NULL (указано минимальным или максимальными значениями), как это:

CREATE VIEW DOC_ACTIVE AS 
SELECT * FROM DOC 
WHERE SYSTIMESTAMP BETWEEN NVL(VALID_FROM, SYSTIMESTAMP) 
         AND NVL(VALID_TO, SYSTIMESTAMP); 
+0

Да, у меня есть первичные ключи. Интересная ссылка в конце. Но ваше описание не обрабатывает перекрывающиеся допустимые диапазоны и будущие допустимые диапазоны моего второго (более полного) варианта. Кроме того, BETWEEN не может обрабатывать значения NULL для бесконечного времени начала/окончания, поэтому для меня это два условия. – ygoe

+0

Я обновил свой ответ, потому что мне нужно было написать больше, чем вписывается в этот комментарий. – Atiris

+0

Хорошо, это другое определение диапазонов допустимости, чем мое. Я не различаю между записью данных и контентом. Возьмем, например, набор правил. До апреля действует определенный набор правил. После этого применяются различные правила. А может быть, только в ноябре, используется временный набор правил. Это 3 версии набора правил. Кроме того, я немного не решаюсь использовать представления в запросах. Я сделал плохой опыт с точки зрения производительности. Oracle не может использовать ключи из источника представления, что делает вещи медленнее, чем необходимо. (Но я здесь не характерен для Oracle). – ygoe

0

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

select * 
from t1 
where VersionId = (select top 1 VersionId 
        from t1 as MostRecentlyValid 
        where MostRecentlyValid.ValidFrom <= @AsOfDate 
          and (MostRecentlyValid.ValidTo >= @AsOfDate 
           or MostRecentlyValid.ValidTo is null) 
          and t1.Id = MostRecentlyValid.Id 
        order by MostRecentlyValid.ValidFrom desc) 

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

Это работало на горстке и тестовых данных, которые я пробовал, но я уверен, что это будет работать для всех случаев.

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

Чтобы присоединиться к другому столу вы могли бы сделать что-то вроде этого

select * 
from (select * 
     from t1 
     where VersionId = (select top 1 VersionId 
       from t1 as MostRecentlyValid 
       where MostRecentlyValid.ValidFrom <= '2014/2/11' 
         and (MostRecentlyValid.ValidTo >= '2014/2/1' 
          or MostRecentlyValid.ValidTo is null) 
         and t1.Id = MostRecentlyValid.Id 
         order by MostRecentlyValid.ValidFrom desc)) as SelectedRecords 
     inner join t2 
      on SelectedRecords.Id = t2.Id 
2

Если вам нужны старых данных являются частью бизнес-логики тогда:

  • Сохранить последнюю версию в главной таблице . (вставка и обновление, удаление просто изменит столбец состояния)
  • Сделайте снимок, когда обновление происходит в таблице подробностей (до того, как какое-либо обновление моментального снимка будет создаваться).

revision history

  • Другой альтернативой будет Event Sourcing модели.

Если старые данные только журнал трассировки изменений затем:

Смежные вопросы