LINUX.ORG.RU

Хранить историю изменений таблицы - другая таблица с ключом актуальной версии [SQL]

 ,


0

1

Я придумал такой паттерн (или антипаттерн?). Наверняка он уже существует. Как он называется? Ну и покритикуйте сам подход.

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

схема.png

CREATE TABLE customer_history (
	id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
	customer_id INT NOT NULL,
	date_modified DATETIME NOT NULL,
	name LONGTEXT NOT NULL,
	comment LONGTEXT
);

CREATE TABLE customer (
	customer_id INT PRIMARY KEY NOT NULL,
	customer_history_id INT,
	FOREIGN KEY (customer_history_id) REFERENCES customer_history(id),
	CONSTRAINT ch_id_unique UNIQUE INDEX (customer_history_id)
);

Получить актуальные данные

SELECT ch.customer_id, ch.name, ch.comment
FROM customer c INNER JOIN customer_history ch ON c.customer_history_id=ch.id
Вставить нового клиента
INSERT INTO customer_history (customer_id, date_modified, name, comment)
VALUES (1, SYSDATE(), 'Рога и копыта', 'первая версия');
INSERT INTO customer (customer_id, customer_history_id)
VALUES(1, LAST_INSERT_ID());
Обновить данные о клиенте без перезаписи
INSERT INTO customer_history (customer_id, date_modified, name, comment)
VALUES (1, SYSDATE(), 'Рога и копыта', 'вторая версия');
UPDATE customer
SET customer_history_id=LAST_INSERT_ID()
WHERE customer_id=1;
Посмотреть историю версий
SELECT *
FROM customer_history
WHERE customer_id=1

Linux тут при том, что примеры кода приведены для MySQL.

★★★

Последнее исправление: damix9 (всего исправлений: 3)

Наверняка он уже существует

Да, много где видел с разными вариациями:

  • Актуальные данные хранятся в самой таблице, а старые версии в таблице *_history. Плюсы данной схемы: а) не надо делать join, чтобы получить актуальные данные; б) можно упростить запись новых данных с помощью триггеров: ты просто делаешь update в таблицу customer, как если бы таблицы customer_history не существовало, а запись в таблицу customer_history выполняет триггер автомагически.
  • Актуальные данные хранятся в самой таблице, а старые версии в той же таблице в отдельном JSON-поле. Плюсы те же самые, как и в прошлом варианте, только ещё проще становится получать предыдущие версии.
  • Актуальные данные хранятся в самой таблице, а старые версии для всех версионируемых таблиц хранятся в одной таблице versions (table_name text, data jsonb, inserted_at timestamp). Плюс — не нужно создавать новую таблицу *_history для каждой таблицы, у которой ты хочешь отслеживать версии. Явный минус — нельзя навесить внешние ключи.

У каждого варианта есть свои минусы, серьёзность которых будет зависеть от используемой СУБД и типовых запросов, которые ты в своём приложении будешь выполнять с этими данными.

theNamelessOne ★★★★★
()
Последнее исправление: theNamelessOne (всего исправлений: 2)

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

untitl3d
()

Я тупо триггером в отдельную таблицу-лог складывал - [кто, когда, что, на что, в какой таблице, в каком поле] поменял.

Но это скорее под эгидой «контроля пользователей», а не для того чтобы быстро в реальном времени искать историю изменений значения поля. Чёт не помню, чтобы хоть раз пригодилось в реальной жизни. Но безопасникам прям бальзам.

Toxo2 ★★★★
()
Последнее исправление: Toxo2 (всего исправлений: 1)

В mysql есть готовый механизм System-Versioned Tables

Можно использовать, например, если нужна какая-то налоговая ставка, которая со временем меняется. В 1С это периодические реквизиты.

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

cylon17
()
Ответ на: комментарий от untitl3d

В итоге не проще ли записывать изменения с таймстампом и просто греп по ид и ордер бай таймстамп?

Запросы типа «выбери имена 50 последних зарегистрированных пользователей» в таком случае несколько усложняются.

theNamelessOne ★★★★★
()

Видел паттерн: все данные хранятся в таблице, для актуальных сделан view

Shadow ★★★★★
()
Ответ на: комментарий от untitl3d

Она не с данными, а с ключами данных. Нет, не проще. Так JOIN с GROUP BY надо. Хороший способ с одной таблицей широко известен, но он другой.

выборка актуальных по дате данных (комментарий)

theNamelessOne,

Актуальные данные хранятся в самой таблице, а старые версии в таблице *_history. Плюсы данной схемы: а) не надо делать join, чтобы получить актуальные данные; б) можно упростить запись новых данных с помощью триггеров: ты просто делаешь update в таблицу customer, как если бы таблицы customer_history не существовало, а запись в таблицу customer_history выполняет триггер автомагически.

Этот способ тоже известен. У него еще плюс: легко массово вставлять одним INSERTом. А минус: нужно иметь вторую таблицу с теми же столбцами - плохо масштабируется, если эта таблица широкая и в нее часто добавляются столбцы, и надо в эту вторую таблицу копировать перед обновлением все данные (если нет триггеров).

У своего способа я такие недостатки пока нашел: чтобы массово вставить, нужно получать IDшники вставленного, и чтобы массово создать новые версии тоже, а это СУБД-специфично и не всегда возможно.

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

damix9 ★★★
() автор топика

Проблема может быть решена введением дополнительной сушности «Операция: что? где? когда?». Например, «Юзер поменял фамилию». То есть на уровне бизнес-логики, а не хранилища.

level1 ★★
()
Ответ на: комментарий от level1

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

damix9 ★★★
() автор топика
Ответ на: комментарий от damix9

В этом способе и обновить с сохранением старого и посмотреть историю сложнее

При обновлении оверхед всего в одну запись в таблицу Операций, зато не требуется отслеживать последовательность версий. И, главное, понятны основания изменений.

level1 ★★
()
Ответ на: комментарий от damix9

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

Как минимум - лишний джойн для получения актуальных данных - уже недочет. Ведь это самая частая операция.

Psilocybe ★★★★
()
Последнее исправление: Psilocybe (всего исправлений: 1)

Иначе говоря, антипатерн тут в том, что нельзя оптимизировать второстепенные запросы за счет более часто используемых.

Psilocybe ★★★★
()

Различные виды аудита изменений в SQL придуманы давно, и продолжают придумываться.

Есть статья на эту тему: https://habr.com/ru/company/first/blog/668516/

Есть автоматизация для PostgreSQL (не единственная, но остальные лень искать в куче закладок на GitHub): https://github.com/supabase/supa_audit

emorozov
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.