Как я уже говорил в комментариях, я не советую загрязнять таблицы данных историей / слухом.
И нет: "двойное управление версиями", предложенное @Josh_Eller в его комментарии, не являетсяХорошее решение также: не только для ненужного усложнения запросов, но и для того, чтобы быть намного более дорогим с точки зрения обработки и фрагментации табличного пространства.
Помните, что операции UPDATE никогда ничего не обновляют.Вместо этого они пишут новую версию строки и отмечают старую как удаленную.Вот почему вакуумные процессы необходимы для дефрагментации табличных пространств, чтобы восстановить это пространство.
В любом случае, кроме неоптимального, этот подход заставляет вас реализовывать более сложные запросы для чтения и записи данных, хотя на самом делеЯ полагаю, что в большинстве случаев вам нужно будет только выбрать, вставить, обновить или даже удалить одну строку и только в конечном итоге просмотреть ее историю.
Так что лучшее решение (IMHO) - просто реализовать схемуВы действительно нуждаетесь в своей основной задаче и внедряете слуховой аппарат в отдельную таблицу и обслуживаете его триггером.
Это было бы гораздо больше:
Надежныйи просто: Поскольку каждый раз вы сосредотачиваетесь на одной вещи (принципы единой ответственности и KISS).
Fast: Аудиторские операции могут выполняться в после срабатывания , поэтому каждый раз при выполнении INSERT , UPDATE или DELETE любой возможной блокировки в трансдействие еще не выполнено, потому что ядро базы данных знает, что его результат не изменится.
Эффективно: Т.е. обновление, конечно, вставит новую строку ипометить старый как удаленный.Но это будет сделано на низком уровне ядром базы данных, и более того: ваши слуховые данные будут полностью нефрагментированы (потому что вы только пишете туда: никогда не обновляйтесь).Таким образом, общая фрагментация всегда будет намного меньше.
Как говорится, как это реализовать?
Предположим, эта простая схема:
create table comments (
text text,
mtime timestamp not null default now(),
id serial primary key
);
create table comments_audit ( -- Or audit.comments if using separate schema
text text,
mtime timestamp not null,
id integer,
rev integer not null,
primary key (id, rev)
);
... а затем эта функция и триггер:
create or replace function fn_comments_audit()
returns trigger
language plpgsql
security definer
-- This allows you to restrict permissions to the auditory table
-- because the function will be executed by the user who defined
-- it instead of whom executed the statement which triggered it.
as $$
DECLARE
BEGIN
if TG_OP = 'DELETE' then
raise exception 'FATAL: Deletion is not allowed for %', TG_TABLE_NAME;
-- If you want to allow deletion there are a few more decisions to take...
-- So here I block it for the sake of simplicity ;-)
end if;
insert into comments_audit (
text
, mtime
, id
, rev
) values (
NEW.text
, NEW.mtime
, NEW.id
, coalesce (
(select max(rev) + 1 from comments_audit where id = new.ID)
, 0
)
);
return NULL;
END;
$$;
create trigger tg_comments_audit
after insert or update or delete
on public.comments
for each row
execute procedure fn_comments_audit()
;
И это все.
Обратите внимание, что при таком подходе вы всегда будете иметь свои текущие комментарии данные в comments_audit .Вместо этого вы могли бы использовать регистр OLD и определять триггер только в операциях UPDATE (и DELETE), чтобы избежать его.
Но я предпочитаю такой подход не только потому, что он дает нам дополнительную избыточность (случайное удаление -если бы это было разрешено или триггер был случайно отключен - на главной таблице, то мы смогли бы восстановить все данные из слуховой таблицы), но также потому, что это упрощает (и оптимизирует) запрос истории, когда это необходимо.
Теперь вам нужно только вставлять, обновлять или выбирать (или даже удалять, если вы немного больше разрабатываете эту схему, т. Е. Вставляете строку с нулями ...) полностью прозрачным способом, как если бы это не былослуховая система.И когда вам нужны эти данные, вам нужно только запросить таблицу слухов.
ПРИМЕЧАНИЕ: Кроме того, вы можете захотеть включить метку времени создания (ctime).В этом случае было бы интересно не допустить его изменения в триггере BEFORE , поэтому я пропустил его (опять же ради простоты), потому что вы уже можете угадать его из mtime s в слуховой таблице (даже если вы собираетесь использовать его в своем приложении, было бы очень желательно добавить его).