PostgreSQL触发器日志仅更改受影响的列

时间:2019-01-26 13:19:38

标签: postgresql

Tracking changes in PostgreSQL上有帮助的文章之后,我得到了一个运行良好的触发函数,该函数可以跟踪与该触发函数关联的所有表的更改。 该函数以JSON的形式将整个OLD NEW行的内容存储为JSON,这很好,但是由于包含许多列,因此很难大致了解实际更改。

我正在寻找一种仅存储NEW和OLD值已更改的列内容的方法。因此,我添加了新列“更改”。

以下是存储历史记录的表:

CREATE TABLE public.t_history
(
  id integer serial,
  tstamptz timestamp with time zone DEFAULT now(),
  schemaname text,
  tabname text,
  operation text,
  who text DEFAULT "current_user"(),
  changes  text,
  new_val json,
  old_val json,
  CONSTRAINT pk_t_history PRIMARY KEY (id)
)

由于trigger-function非常通用,因此我想使用一种比较两个json列new_val和old_val的方法,或者使用foreach循环查看每列而不指定硬编码列名的方法。结果,实际更改应存储在“更改”列中。

这是触发功能的样子:

REATE OR REPLACE FUNCTION public.change_trigger()
  RETURNS trigger AS
$BODY$
        BEGIN
                IF      TG_OP = 'INSERT'
                THEN
                        INSERT INTO public.t_history (tabname, schemaname, operation, new_val)
                                VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP, row_to_json(NEW));
                        RETURN NEW;

                ELSIF   TG_OP = 'UPDATE'
                THEN
                        INSERT INTO public.t_history (tabname, schemaname, operation, new_val, old_val)
                                VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP,
                                        row_to_json(NEW), row_to_json(OLD));
                        RETURN NEW;
                ELSIF   TG_OP = 'DELETE'
                THEN
                        INSERT INTO public.t_history (tabname, schemaname, operation, old_val)
                                VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP, row_to_json(OLD));
                        RETURN OLD;
                END IF;
        END;

$BODY$
  LANGUAGE plpgsql VOLATILE SECURITY DEFINER
  COST 100;

1 个答案:

答案 0 :(得分:0)

是的,您确实需要升级。 9.2中关于此的可能性相当有限。因此,以下答案假定最低为9.4,这是目前仍受支持的最低版本。


您可以使用json_each_text()生成一组键值对,然后过滤具有相同键的不同值。

然后,您可以从那里构建一些JSON,例如,使用json_build_object()每列一个JSON对象,并使用json_agg()将它们聚合到JSON数组中。您没有指定所需的确切格式。所以这只是一个例子。您可能在那里提出了不同的想法。

SELECT json_agg(json_build_object('column',
                                  old2.key,
                                  'old',
                                  old2.value,
                                  'new',
                                  new2.value))
       FROM (VALUES (1,
                     2,
                     3)) old(i,
                             j,
                             k)
            CROSS JOIN (VALUES (1,
                                3,
                                4)) new(i,
                                        j,
                                        k)
            CROSS JOIN LATERAL json_each_text(row_to_json(old)) old2
            CROSS JOIN LATERAL json_each_text(row_to_json(new)) new2
       WHERE old2.key = new2.key
             AND old2.value <> new2.value;

db<>fiddle

注意:VALUES x VALUES交叉联接仅用于演示,因此我有一个new和一个old,但之后没有触发所有。当然,在触发器中,您已经拥有newold,可以跳过它们并使用FROM子句,例如:

FROM json_each_text(row_to_json(old)) old2
     CROSS JOIN json_each_text(row_to_json(new)) new2