Вопрос

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

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

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

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

В принципе, я должен быть в состоянии восстановить данные для любого момента времени.

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

valid_from
valid_to
edited_by

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

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

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

Однако мне интересно, есть ли другой способ.

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

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

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

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

Спасибо.

Это было полезно?

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

И справочные таблицы:Тип датчика;Тип оповещения;Тип действия.

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

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

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

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

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

  • Полная реляционная способность;Декларативная ссылочная целостность и т.д.

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

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

  • Тот Самый AuditedDtm показывает всю серию UpdatedDtms для любого заданного ключа;и таким образом, я использовал его для "разделения" реального ключа во временном смысле.

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

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

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

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

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

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

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

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

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

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) Все столбцы должны иметь правила и контрольные ограничения, ограничивающие диапазон их значений.Это в дополнение к тому факту, что все ВСТАВКИ / ОБНОВЛЕНИЯ / удаления являются программными в рамках хранимых процедур, поэтому несчастных случаев не происходит, и люди не подходят к базе данных и не запускают против нее команды (за исключением SELECTS).

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

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

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

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

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

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

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

ОК.Затем новый Sensor с теми же параметрами, действительно новый, он имеет новый SensorNo, и не зависит от каких - либо предыдущих логических Sensor.Мы можем добавить IsObsolete ЛОГИЧЕСКОЕ значение для четырех идентифицирующих таблиц;теперь он определен как адекватный.Удаление теперь является Мягким удалением.

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

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

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

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

(4) Книга, на которую Ссылаются

Окончательный и основополагающий текст таков Временные данные и реляционная модель Си Джей Дейт, Х. Дарвен, Н А Лоренцос.Например, те из нас, кто принимает RM, знакомы с расширениями и с тем, что требуется от преемника RM;а не каким-то другим методом.

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

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

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

  • Мгновенный, как ДАТА-ВРЕМЯ, например.Обновленный DTM

  • Интервал как ЦЕЛОЕ ЧИСЛО, четко идентифицирующее Единицу измерения в названии столбца, например.ИнтервалСек

  • Точка.Зависит от соединения или дизъюнкта.

    • Для соединения, которым является это требование, применяется (4.1):используйте одну ДАТУ И ВРЕМЯ;конец периода может быть получен из начала периода следующей строки.
    • Для разобщенных периодов, да, вам нужно 2 x DateTimes, например, RentedFrom и еще RentedTo с промежутками между ними.

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

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

(4.5) Возникает вопрос, почему 528-страничный "учебник" доступен бесплатно в Интернете в плохом формате PDF.

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

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

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

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

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

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

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

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

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

    • Обратите внимание, что я добавил (1.1) выше.

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

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

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

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

  • необходимо ориентироваться по подтипам и т.д. Только когда этот конкретный контекст имеет значение.

Другие советы

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top