Историческая / проверяемая база данных - PullRequest
13 голосов
/ 20 декабря 2010

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

Однако - когда элемент редактируется или удаляется, мне нужно сохранить старые данные;Мне нужно, чтобы увидеть, какие данные были до изменения.

В базе данных также есть нередактируемые элементы, такие как «чтения».Они больше похожи на бревно.Показания регистрируются для датчиков, потому что это показания для конкретного датчика.

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

По сути, я должен быть в состоянии восстановить данные за любой момент времени.

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

valid_from
valid_to
edited_by

Если valid_to = 9999-12-31 23:59:59, то это текущая запись.Если valid_to равно valid_from, то запись удаляется.

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

Я могу избежать триггеров, используярасширение базы данных "PostgreSQL".Это обеспечивает тип столбца с именем «period», который позволяет хранить период времени между двумя датами, а затем позволяет выполнять ограничения CHECK для предотвращения перекрывающихся периодов.Это может быть ответом.

Интересно, есть ли другой способ?

Я видел, как люди упоминали использование специальных исторических таблиц, но мне не очень нравилась мысль о поддержке2 таблицы для почти каждой 1 таблицы (хотя это все еще возможно).

Может быть, я мог бы сократить свою первоначальную реализацию, чтобы не беспокоиться о проверке согласованности записей, которые не являются "текущими" - т.е.проверить ограничения на записи, где valid_to равен 9999-12-31 23:59:59.В конце концов, люди, которые используют исторические таблицы, похоже, не проверяют ограничения этих таблиц (по той же причине, вам нужны триггеры).

У кого-нибудь есть какие-либо мысли по этому поводу?

PS - в названии также упоминается проверяемая база данных.В предыдущей системе, о которой я упоминал, всегда есть поле edited_by.Это позволило отслеживать все изменения, чтобы мы всегда могли видеть, кто изменил запись.Не уверен, какая разница, что может иметь значение.

Спасибо.

Ответы [ 3 ]

31 голосов
/ 01 января 2011

Пересмотрено 01 января 11

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

  • Чтобы обеспечить это требование, совсем не нужно: триггеры;массовое дублирование;нарушение целостности;и т. д.

  • Это не классическое временное требование, поэтому не нужно для возможности "period", но вы можете .

  • ValidFrom и ValidTo - это ошибка нормализации: ValidTo - это данные, которые легко получить;ValidTo в любой строке дублируется, в ValidFrom следующей строки;у вас есть аномалия обновления (когда вы обновляете один столбец в одной строке, вам дополнительно необходимо обновить другой столбец в следующей строке);Вы должны использовать фиктивное значение для «текущего».

    • Все ненужное, используйте только ValidFrom и сохраняйте db в чистоте и чистоте 5NF.

    • Предостережение, если PostgreSQL не может выполнитьПодзапросы, не попадая в кучу (аля Oracle), затем нормально, Kep ValidTo.

Все эти вещи доступны для редактирования в системепо пользователям, и могут быть удалены.

Ну нет.Это база данных, содержащая важную информацию;с ссылочной целостностью, а не блокнотом, поэтому пользователь не может просто подойти к нему и «удалить» что-то.Это будет противоречить требованию пользователей о ведении исторических данных (в разделе «Чтение; Предупреждение; Подтверждение; Действие; Загрузить»).

  • Каскадное удаление не допускается.Эти функции являются флажками для не-баз данных, типов MS Access.Для реальных баз данных ограничения RI препятствуют удалению родителей с детьми.

  • Первичные ключи не могут (не должны) изменяться.Например.Идентификатор пользователя;LocationId;NetworkSlaveCode никогда не меняется;помните, они тщательно считаются идентификаторами .Одной из характеристик ПК является то, что они стабильны.

  • Вы можете добавлять новых пользователей;Вы можете изменить текущий Имя пользователя;но вы не можете удалить пользователя, у которого есть записи в разделе «Загрузка», «Подтверждение», «Действие».

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

Также не включает: Загрузки;Выражение признательности;Действия.

И Справочные таблицы: SensorType;AlertType;ActionType.

И новые таблицы истории: они вставляются, но не могут быть обновлены или удалены.

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

  • Хорошо, теперь вы понимаете, что LocationId (FK) в Sensor не изменится;нет массового дублирования и т. д.?Во-первых, нет проблем (и есть в этой глупой книге!), Во-вторых, экспоненциально ухудшается.

  • IsObsolete не соответствует вашим требованиям. (см. Ниже)

  • UpdatedDtm в любой реальной строке (Reading и т. Д.) Идентифицирует строку истории родительского (от FK до Sensor) (ее AuditedDtm), который действовал в то время.

  • Полная реляционная возможность;Декларативная целостность и т. Д.

  • Поддерживать IDEF1X, реляционная концепция сильных идентификаторов ... Существует только одна текущая родительская строка (например, Местоположение)

  • Строки в Истории - это Изображения текущей строки, до того как она была изменена, в указанном AuditedDtm.В текущей строке (не в истории) отображается последний обновленный элемент UpdateDtm, когда строка была изменена.

  • AuditedDtm показывает всю серию UpdatedDtms для любого заданного ключа; и поэтому я использовал его для «разделения» реального ключа во временном смысле.

Все, что требуется, это таблица истории для каждой изменяемой таблицы. Я предоставил таблицы Hiistory для четырех идентификационных таблиц: Location; Датчик; NetworkSlave; и пользователь.

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

Модель данных

Ссылка на Модель данных датчика с историей (Страница 2 содержит таблицы истории и контекст).

Читатели, не знакомые со стандартом реляционного моделирования, могут найти нотацию IDEF1X полезными.

Ответ на комментарий

(1) Моя первая проблема - это проблема ссылочной целостности с историческими данными, в которой я не уверен, что есть, и если есть, я не уверен, как она работает. Например, в SensoryHistory можно было бы добавить запись, у которой было значение UpdateDtm, указывающее дату и время до появления самого местоположения, если вы понимаете, что я имею в виду. Является ли это на самом деле проблемой, я не уверен - принудительное выполнение этого может быть чрезмерным.

(Вы подняли аналогичную проблему в другом вопросе.) Возможно, что у dbs, которые вы испытали, на самом деле не было ссылочной целостности; что отношения были там только для документации; что RI был «реализован в коде приложения» (что означает, что RI отсутствует).

Это база данных SQL стандарта ISO / IEC / ANSI. Это позволяет декларативную ссылочную целостность. Каждая строка отношения реализована в виде ссылки PK :: FK, фактического ограничения, которое объявлено. Например:

<code>CREATE TABLE Location
    ...
    CONSTRAINT UC_PK
        PRIMARY KEY (LocationId)
    ...
CREATE TABLE Sensor
    ...
    CONSTRAINT UC_PK
        PRIMARY KEY (LocationId, SensorNo)
    CONSTRAINT Location_Sensor_fk
        FOREIGN KEY (LocationId)
        REEFERENCES Location(LocationId)
    ...
CREATE TABLE SensorHistory
    ...
    CONSTRAINT UC_PK
        PRIMARY KEY (LocationId, SensorNo, UpdatedDtm))
    CONSTRAINT Sensor_SensorHistory_fk
        FOREIGN KEY (LocationId, SensorNo)
        REEFERENCES Sensor (LocationId, SensorNo)
    ...
Эти объявленные ограничения применяются сервером; не через триггеры; не в коде приложения. Это значит:
  • A Sensor с LocationId, который не существует в Location, не может быть вставлен
  • A LocationId в Location, в котором есть строки в Sensor, удалить нельзя
  • A SensorHistory с LocationId+SensorNo, который не существует в Sensor, не может быть вставлен
  • A LocationId+SensorNo в Sensor, в котором есть строки в SensorHistory, удалить нельзя.

(1.1) Все столбцы должны иметь ПРАВИЛА и ПРОВЕРКУ Ограничения, чтобы ограничить их диапазон значений. Это в дополнение к тому факту, что все INSERT / UPDATE / DELETE являются программными, в хранимых процессах, поэтому несчастных случаев не происходит, и люди не подходят к базе данных и не запускают против нее команды (за исключением SELECTS).

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

в SensoryHistory можно было бы добавить запись, для которой было указано UpdateDtm, указывающее дату и время до появления самого Местоположения, если вы понимаете, что я имею в виду

запрещено. Таким образом, вставляется SensorHistory с UpdatedDtm раньше, чем сам Sensor. Но процы не являются декларативными правилами. Однако, если вы хотите быть уверены вдвойне (а я имею в виду вдвойне, потому что ВСТАВКИ все через proc, прямая команда пользователей), тогда, конечно, вы должны использовать триггер. Для меня это слишком.

(2) как указать удаление? Я мог бы просто добавить флаг к неисторической версии таблицы.

Пока не уверен. Например. Согласны ли вы с тем, что при удалении Sensor оно является окончательным ... (да, история сохраняется) ... и затем, когда к Location добавляется новый Sensor, он будет иметь новый SensorNo ... логически не заменяется Sensor на новый, с разрывом во времени или без него?

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

И "удалить" Locations, NetworkSlaves, а также Users.

Ok. Тогда новый Sensor с теми же параметрами действительно новый, он имеет новый SensorNo и не зависит от любого предыдущего логического Sensor. Мы можем добавить IsObsolete BOOLEAN к четырем идентифицирующим таблицам; теперь он идентифицирован как адекватный. Удалить теперь мягкое удаление.

(2.1) Для NetworkSensor и LoggerSensor, которые фактически зависят от двух родителей: они устарели, если один из их родителей устарел. Поэтому нет смысла давать им столбец IsObsolete, который имеет двойное значение и может быть получен из соответствующего родителя.

(2.2) Для ясности, пользователи не могут удалять строки из таблиц транзакций и истории, верно?

(3) При обновлении таблицы, какой метод будет лучше всего вставить новую строку в историческую таблицу и обновить основную таблицу? Может быть, просто обычные операторы SQL внутри транзакции?

Да. Это классическое использование Транзакции, согласно ACID Properties, это Atomic; он либо завершается успешно, либо завершается неудачей (чтобы повторить попытку позже, когда проблема будет устранена).

(4) Справочная книга

Окончательный и оригинальный текст: Временные данные и реляционная модель C J Date, H Darwen, N A Lorentzos. Например, те из нас, кто принимает RM, знакомы с расширениями и тем, что требуется от преемника RM; а не какой-то другой метод.

Указанная книга ужасна и бесплатна. PDF не PDF (без поиска; без индексации). Открытие моего MS и Oracle говорит; несколько хороших кусочков в куче пуха. Много искажений. Не стоит подробно отвечать (если вы хотите получить правильный обзор, откройте новый вопрос).

(4.1) ValidTo в дополнение к ValidFrom. Серьезная ошибка (как указано в верхней части моего ответа), которую совершает книга; затем кропотливо решает. Во-первых, не совершайте ошибку, а во-вторых, вам нечего решать. Насколько я понимаю, это устранит ваши триггеры.

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

  • Мгновенно как DATETIME, например. UpdatedDtm

  • Интервал как INTEGER, четко идентифицирующий Единицу в названии столбца, например. IntervalSec

  • Период. Зависит от соединенного или несвязанного.

    • Для конъюнктуры, к которой относится это требование, (4.1): используйте один DATETIME; конец периода может быть получен из начала периода следующей строки.
    • Для разделенных периодов да, вам нужно 2 x DATETIME, например, RentedFrom и RentedTo с промежутками между ними.

(4.3) Они связываются с «временным первичным ключом», что усложняет код (в дополнение к требованию триггеров контролировать аномалию обновления). Я уже доставил чистый (испытанный и проверенный) временный первичный ключ.

(4.4) Они связываются с фиктивными значениями, нереальными значениями и значениями Null для «сейчас». Я не допускаю такие вещи в базе данных. Так как я не храню дублированный ValidTo, у меня нет проблемы, здесь нечего решать.

(4.5) Следует задаться вопросом, почему «учебник» на 528 страниц доступен бесплатно в Интернете, в плохой PDF-форме.

(5) Я [пользователь] мог бы спокойно удалить, например, все строки LocationHistory (оставив только текущую версию в таблице Location) - даже если может существовать строка SensorHistory, которая концептуально «принадлежит» к предыдущей версии Местоположения, если это имеет смысл.

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

  • В реальной (стандартной ISO / IEC / ANSI SQL) базе данных мы делаем не GRANT INSERT / UPDATE / DELETE пользователям. Мы ПРЕДОСТАВЛЯЕМ ВЫБОР и ССЫЛКИ * только 1278 * (для выбранных пользователей) Все ВСТАВКИ / ОБНОВЛЕНИЯ / УДАЛЕНИЯ кодируются в Транзакциях, что означает хранимые процедуры. Затем мы предоставляем GREC EXEC для каждого сохраненного процесса выбранным пользователям (используйте ROLES для сокращения администрирования).

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

    • Не пишите процедуру для удаления из любой таблицы истории. Эти строки не должны быть удалены. В этом случае отсутствие разрешения и отсутствие кода является Ограничением.

    • Технически, все строки Истории действительны, нет Периода для беспокойства. Самая старая строка LocationHistory содержит предварительное изображение исходной строки Location до ее изменения. Самые младшие строки LocationHistory - это изображение перед текущей строкой Location. Таким образом, каждая промежуточная строка LocationHistory действительна и применяется к промежуточному периоду.

    • Нет необходимости «обрезать» или искать несколько строк LocationHistory, которые можно удалить на основании того, что они применяются к неиспользуемому периоду: все они используются . (Определенно, без необходимости проверять какое-либо отображение дочерних объектов Location на любую строку (и) LocationHistory, чтобы доказать это.)

    • Итог: пользователь не может удалить таблицу из любой истории (или транзакции).

    • Или ты опять имеешь в виду что-то другое?

    • Примечание. Я добавил (1.1) выше.

(6) Исправлена ​​одна ошибка в DM. Alert является выражением Reading, а не Sensor.

(7) Исправил бизнес-правила в другом вопросе / ответе, чтобы отразить это; и новые правила, изложенные в этом вопросе.

(8) Понимаете ли вы / цените ли вы, что, поскольку у нас полностью совместимая с IDEF1X модель, повторно Идентификаторы :

  • Идентификаторы передаются по всей базе данных, сохраняя свою силу. Например. при перечислении Acknowledgements они могут быть соединены напрямую с Location и Sensor; промежуточные таблицы не нужно читать (и они должны быть, если используются ключи Id). Вот почему на самом деле требуется меньше объединений в реляционной базе данных (и больше соединений требуется в ненормализованной).

  • Подтипы и т. Д. Необходимо перемещаться только , когда этот конкретный контекст имеет значение.

1 голос
/ 20 декабря 2010

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

Другой вариант - записывать все изменения, которые позволяют кому-то «воспроизвести» произошедшее и отследить его.Каждое изменение заносится в таблицу или поле (в зависимости от ваших потребностей), которое отслеживает, кто, когда и что было изменено на что, т. Е. 31 декабря 2010 года Боб изменил статус с «Открыто» на «Закрыто».

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

0 голосов
/ 21 декабря 2010

В зависимости от вашего бюджета и / или среды, вы можете рассмотреть возможность использования функции архивации флэш-памяти Oracle.

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

SELECT *
FROM important_data
AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '5' DAY)

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

...