这是一个由表上的插入,更新或删除调用的触发器。保证调用表具有受影响的所有列,并且还存在删除表。
CREATE OR REPLACE FUNCTION sample_trigger_func() RETURNS TRIGGER AS $$
DECLARE
operation_code char;
table_name varchar(50);
delete_table_name varchar(50);
old_id integer;
BEGIN
table_name = TG_TABLE_NAME;
delete_table_name = TG_TABLE_NAME || '_deletes';
SELECT SUBSTR(TG_OP, 1, 1)::CHAR INTO operation_code;
IF TG_OP = 'DELETE' THEN
OLD.mod_op = operation_code;
OLD.mod_date = now();
RAISE INFO 'OLD: %', (OLD).name;
EXECUTE format('INSERT INTO %s VALUES %s', delete_table_name, (OLD).*);
ELSE
EXECUTE format('UPDATE TABLE %s SET mod_op = %s AND mod_date = %s'
, TG_TABLE_NAME, operation_code, now());
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
ELSE
分支触发无限循环。可能会有更多问题。
如何解决?
答案 0 :(得分:2)
ELSE
分支可以从根本上简化。但是还有一些事情效率低下/不准确/危险:
CREATE OR REPLACE FUNCTION sample_trigger_func()
RETURNS TRIGGER AS
$func$
BEGIN
IF TG_OP = 'DELETE' THEN
RAISE INFO 'OLD: %', OLD.name;
EXECUTE format('INSERT INTO %I SELECT ($1).*', TG_TABLE_NAME || '_deletes')
USING OLD #= hstore('{mod_op, mod_datetime}'::text[]
, ARRAY[left(TG_OP, 1), now()::text]);
RETURN OLD;
ELSE -- insert, update
NEW.mod_op := left(TG_OP, 1);
NEW.mod_datetime := now();
RETURN NEW;
END IF;
END
$func$ LANGUAGE plpgsql;
在ELSE
分支中,只需直接分配给NEW
即可。不需要更多动态SQL - 它会再次触发相同的触发器,从而导致无限循环。这是主要错误。
RETURN NEW;
构造之外的 IF
会破坏DELETE
的触发器功能,因为NEW
未分配给DELETE。
一个关键功能是使用hstore
and the hstore operator #=
动态更改众所周知的行类型的两个选定字段 - 即 unknown 在编写代码时。这样您就不会篡改原始OLD
值,如果您在事件链中有更多触发器,则可能会产生令人惊讶的副作用。
OLD #= hstore('{mod_op, mod_datetime}'::text[]
, ARRAY[left(TG_OP, 1), now()::text]);
必须安装附加模块hstore
。详细说明:
在此处使用hstore(text[], text[])
变体构建包含多个字段的hstore
值。
plpgsql中的赋值运算符为:=
:
请注意,我使用了列名mod_datetime
而不是误导性的mod_date
,因为该列显然是timestamp
而不是date
。
我加入了其他一些改进。触发器本身应如下所示:
CREATE TRIGGER insupdel_bef
BEFORE INSERT OR UPDATE OR DELETE ON table_name
FOR EACH ROW EXECUTE PROCEDURE sample_trigger_func();