Вы можете использовать функции, которые я написал, для хранения исторических данных.Краткое описание:
Исторические данные хранятся в отдельной схеме с именем аудит .Таким образом, первым шагом будет создание этой схемы:
CREATE SCHEMA audit;
В схеме аудита можно найти точные копии таблиц из открытых, которые создаются динамически при первом изменении данных в общедоступной схеме.Таким образом, до первого использования схемы аудита базы данных остается пустой, пока пользователь не выполнит свою первую вставку в одну из таблиц.
Функция _audit_table_creator (name) затем копирует структуру таблицы из общедоступной схемы и создает ту же таблицу в схеме аудита с некоторыми дополнительными столбцами, которую я назвал «штамп аудита».В штампе аудита хранится информация о:
- времени удаления записи (shift_time),
- пользователе, который сделал удаление (who_altered),
- 'DELETE' штампе(alter_type) и
- столбец, который был изменен - только для операций обновления (change_columns);
Я думаю, что наибольшим преимуществом этого решения является то, что поддерживаются составные первичные ключи (функция _where_clause_creator (text []) создает правильное предложение where для таблицы, вызываемой триггером, путем объединения строк в правильном порядке);
Просмотр исторических записей:
Каждый раз, когда мы хотим получить архивные данные, мы должны использовать псевдонимы, то есть, чтобы извлечь исторические данные о пользователе, у которого user_id = 5, нужно написать:
SELECT * FROM audit.users WHERE user_id = 5;
Так чтоВ обеих схемах могут использоваться одни и те же запросы, но для извлечения исторических данных необходимо добавить «аудит».перед именем таблицы.
Возможно, вы захотите автоматически создать триггеры удаления для всех таблиц в базе данных, если вы это сделаете, вы можете просто выполнить запрос:
SELECT * FROM audit_gen_triggers();
Основнойфункция:
CREATE OR REPLACE FUNCTION audit_delete()
RETURNS trigger AS
$BODY$DECLARE
t_name text;
query_op text;
primary_keys text;
c record;
key_arr text;
keys_arr text;
p_r text;
BEGIN
t_name := 'audit.' || TG_TABLE_NAME;
IF NOT EXISTS(SELECT 1 FROM pg_tables WHERE schemaname = 'audit' AND
tablename = TG_TABLE_NAME) THEN
EXECUTE 'SELECT _audit_table_creator(table_name := ($1)::name)'
USING TG_TABLE_NAME;
END IF;
FOR c IN SELECT pg_attribute.attname
FROM pg_index, pg_class, pg_attribute
WHERE
pg_class.oid = TG_TABLE_NAME::regclass AND
indrelid = pg_class.oid AND
pg_attribute.attrelid = pg_class.oid AND
pg_attribute.attnum = ANY(pg_index.indkey) AND
indisprimary LOOP
key_arr := c.attname || ', ($1).' || c.attname;
keys_arr := concat_ws(',', keys_arr, key_arr);
END LOOP;
keys_arr := '{' || keys_arr || '}';
EXECUTE 'SELECT _where_clause_creator(VARIADIC ($1)::text[])'
INTO p_r USING keys_arr;
-- raise notice 'tablica where: %', p_r;
-- zapisz do tabeli audytowanej wszystkie usuniete wartosci
query_op := 'INSERT INTO '|| t_name ||
' SELECT NEXTVAL(''serial_audit_'
|| TG_TABLE_NAME ||'''::regclass),
CURRENT_USER, ''' || TG_OP || ''',
NULL,
NOW(),
($1).*
FROM ' || TG_TABLE_NAME ||
' WHERE ' || p_r;
EXECUTE query_op USING OLD;
RETURN OLD;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
триггер:
CREATE TRIGGER table_name_delete_audit
BEFORE DELETE
ON table_name
FOR EACH ROW
EXECUTE PROCEDURE audit_delete();
другие используемые функции:
CREATE OR REPLACE FUNCTION _array_position(anyarray, anyelement)
RETURNS integer AS
$BODY$
SELECT i
FROM (SELECT generate_subscripts($1, 1) as i, unnest($1) as v) s
WHERE v = $2
UNION ALL
SELECT 0
LIMIT 1;
$BODY$
LANGUAGE sql STABLE
COST 100;
CREATE OR REPLACE FUNCTION _audit_table_creator(table_name name)
RETURNS void AS
$BODY$
DECLARE
query_create text;
BEGIN
query_create := 'DROP TABLE IF EXISTS temp_insert;
DROP TABLE IF EXISTS temp_insert_prepared';
EXECUTE query_create;
query_create := 'DROP SEQUENCE IF EXISTS serial_audit_' || table_name;
EXECUTE query_create;
query_create := 'CREATE SEQUENCE serial_audit_' || table_name || ' START 1;
ALTER TABLE serial_audit_' || table_name ||
' OWNER TO audit_owner;';
EXECUTE query_create;
query_create := 'CREATE TEMPORARY TABLE temp_insert_prepared ( '
|| table_name || '_audit_id bigint DEFAULT
nextval(''serial_audit_' || table_name || '''::regclass),
who_altered text DEFAULT CURRENT_USER,
alter_type varchar(6) DEFAULT ''INSERT'',
changed_columns text,
shift_time timestamp(0) without time zone DEFAULT NOW(),
PRIMARY KEY(' || table_name || '_audit_id )) ON COMMIT DROP';
EXECUTE query_create;
query_create := 'CREATE TEMPORARY TABLE temp_insert ON COMMIT DROP AS TABLE
' || table_name;
EXECUTE query_create;
query_create := 'CREATE TABLE audit.' || table_name ||
' AS SELECT a.*, b.* FROM temp_insert_prepared a, temp_insert b
WITH NO DATA';
EXECUTE query_create;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
CREATE OR REPLACE FUNCTION _where_clause_creator(VARIADIC keys_given text[])
RETURNS text AS
$BODY$
DECLARE
x text;
where_clause text;
BEGIN
FOREACH x IN ARRAY keys_given LOOP
IF ((SELECT _array_position(keys_given, x))%2) <> 0 THEN
where_clause := concat_ws(' AND ', where_clause, x);
ELSE
where_clause := concat_ws(' = ', where_clause, x);
END IF;
END LOOP;
RETURN where_clause;
END;
$BODY$
LANGUAGE plpgsql STABLE
COST 100;
CREATE OR REPLACE FUNCTION audit_gen_triggers()
RETURNS void AS
$BODY$
DECLARE
r record;
query_create text;
BEGIN
FOR r IN SELECT table_name
FROM information_schema.tables
WHERE table_schema = current_schema AND
table_type = 'BASE TABLE' LOOP
query_create := 'DROP TRIGGER IF EXISTS ' || r.table_name || '_delete_audit ON '
|| r.table_name || ' CASCADE;
CREATE TRIGGER ' || r.table_name || '_delete_audit
BEFORE DELETE
ON ' || r.table_name || '
FOR EACH ROW
EXECUTE PROCEDURE audit_delete();';
EXECUTE query_create;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;