我们有一个应用程序,它将根据用户请求从表中删除一行。我无法更改应用程序代码。但是,我想根据正在删除的行的信息,将一行插入到另一个表(有点像日志日志)中,其中包含来自其他几个表的信息。
如何在PostgreSQL中实现这一目标?
答案 0 :(得分:14)
编写触发功能。像这样:
CREATE OR REPLACE FUNCTION trg_backup_row()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO other_tbl
SELECT (OLD).*, t.other_col -- all columns of from old table
-- SELECT OLD.col1, OLD.col2, t.other_col -- alternative: some cols from old tbl
FROM third_tbl t
WHERE t.col = OLD.col -- link to third table with info from deleted row
AND <unique_condition_to_avoid_multiple_rows_if_needed>;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
触发器ON DELETE
。像这样:
CREATE TRIGGER delaft
AFTER DELETE
ON tbl
FOR EACH ROW
EXECUTE PROCEDURE trg_backup_row();
要返回旧表中的所有列,请使用语法(OLD).*
。请参阅有关accessing composite types的手册。或者,OLD.*
也是有效的语法,因为OLD
隐式添加到FROM
子句中。但是对于VALUES
表达式,它必须是(OLD).*
。像:
INSERT INTO other_tbl
VALUES((OLD).*, some_variable)
您可以包含我演示的任何其他表格中的值。只需确保获得一行,或者您创建多个条目。
当触发器触发AFTER
事件时,该函数可以RETURN NULL
。
回应@ couling的注意评论。
虽然外键可以是declared as DEFERRED
,但这只会推迟完整性检查,而不是删除本身。在{{1>}外键的或之前执行的触发器中删除的行将在ON DELETE CASCADE
时不再可见触发器被调用。 (这一切都明显发生在一个交易中。这些细节都不适用于其他交易,这些交易将看到全部或全部效果。有关MVCC model and transaction isolation的更多信息,请参阅手册。)
因此,如果您希望在AFTER DELETE
中包含来自行的值,请务必在之前调用此触发器 。
您可能需要触发INSERT
。
或者它可能意味着您必须相应地订购触发器,显然BEFORE DELETE
触发器会在BEFORE
触发之前触发。同一级别的触发器在alphabetical order中执行。
但是,只要我在这里非常精确,我可能还会补充说,对其他AFTER
触发器中的行(或依赖行)所做的更改也只有在之前调用时才可见/ em>这一个。
我建议将它设为BEFORE
触发器是因为它不太容易出现并发症,如果其他触发器可能在操作中途取消(回滚)AFTER
,则不会更容易出现 - 只要没有以上适用。
答案 1 :(得分:4)
您可能希望使用我为保留历史数据而编写的函数。简短描述:
历史数据保存在名为审核的单独模式中。因此,第一步是创建此架构:
CREATE SCHEMA audit;
在审计模式中,可以从公共中找到表的精确副本,这些表是在公共模式中的数据发生第一次更改时动态创建的。因此,在第一次使用数据库之前,审计模式保持为空,直到用户将第一次插入到其中一个表中。
函数_audit_table_creator(name)正在从公共模式复制表的结构,并在审计模式中创建具有一些其他列的相同表,我称之为“审计标记”。审计标记保存有关的信息:
我认为此解决方案的最大优势是支持 复合主键 (函数_where_clause_creator(text [])为触发器调用的表创建适当的where子句以正确的顺序连接字符串);
查看历史记录
每当我们想要检索档案数据时,我们必须使用别名,即检索有关user_id = 5的用户的历史数据:
SELECT * FROM audit.users WHERE user_id = 5;
因此,可以在两个模式中使用相同的查询,但要检索历史数据,必须在表名之前添加“audit。”。
您可能希望一次为数据库中的所有表自动创建删除触发器,如果您这样做,则可以执行查询:
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;