更新后显示新列值的 Postgres 函数为 JSON

Postgres function to show new column values after update as JSON

我正在尝试为 AFTER UPDATE 触发器编写一个 postgres 函数,该函数创建一个 JSON 对象,其中包含列名称及其仅针对已更改列的更新值。我正在尝试创建一个可以用于任何 table 的通用函数,因此使用 Record 作为函数参数。 oldnew 来自触发器的 OLDNEW 变量。最终目标是将返回的 JSON 保存到审核 table.

的 JSON 字段中
CREATE OR REPLACE FUNCTION row_updates(old RECORD, new RECORD) RETURNS JSON AS
$$
DECLARE
    updates JSON;
BEGIN
    WITH columns AS (
        SELECT json_object_keys(row_to_json(new)) "column"
    )
    SELECT
        json_object_agg("column", new_value) INTO updates
    FROM (
        SELECT 
            "column",
            (row_to_json(new)->"column" #>> '{}') as new_value,
            (row_to_json(old)->"column" #>> '{}') as old_value
        FROM
            columns
    ) changes
    WHERE 
        new_value IS DISTINCT FROM old_value;

    RETURN updates;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION audit_change() RETURNS TRIGGER AS
$$
DECLARE
    updates JSON;
BEGIN
    IF (TG_OP = 'INSERT') THEN
        raise NOTICE 'Logging insert on relation (%.%) %', TG_TABLE_SCHEMA, TG_TABLE_NAME;

        RETURN NEW;
    ELSIF (TG_OP = 'UPDATE') THEN
        updates := row_updates(OLD, NEW)
        raise NOTICE 'Logging update on relation (%.%) %', TG_TABLE_SCHEMA, TG_TABLE_NAME, updates;
        RETURN NEW;
    ELSIF (TG_OP = 'DELETE') THEN
        raise NOTICE 'Logging delete on relation (%.%) %', TG_TABLE_SCHEMA, TG_TABLE_NAME;
        RETURN OLD;
    END IF;
END;
$$ LANGUAGE plpgsql;

我收到这个错误: ERROR: PL/pgSQL functions cannot accept type record.

有没有更好的方法来完成这个?显然我想在非触发器函数中执行它,所以我可以在不同的触发器中为不同的 table 重用它。另外,如果可能的话,我可能想针对 AFTER INSERTAFTER DELETE 触发器进行调整,所以我不想在触发器函数中分支 TG_OP 并重复 SQL 中显示的逻辑以上。

最终目标:

=> select * from org_user;

 id | org_id | user_id 
----+--------+---------
  1 |      1 |       1
 23 |      1 |       3

=> update org_user set org_id=3, user_id = 4 where id = 23; 

-- trigger creates following row in audit table

 id | relation  | record_id | updates 
----+-----------+-----------+--------------
  1 |  org_user |     23    | {'org_id': 3, 'user_id': 4} 

创建一个比较两个 JSONB 值的函数:

create or replace function jsonb_diff(jsonb, jsonb)
returns jsonb language sql immutable as $$
    select jsonb_object_agg(n.key, n.value)
    from jsonb_each() o
    join jsonb_each() n on o.key = n.key
    where o.value <> n.value;
$$;

并在你的触发器函数中使用它:

    updates := jsonb_diff(to_jsonb(OLD), to_jsonb(NEW));
    raise NOTICE 'Logging update on relation (%.%) %', TG_TABLE_SCHEMA, TG_TABLE_NAME, updates;
    RETURN NEW;

顺便说一下,在 Postgres 11+ 中,您可以使用 record 类型的函数参数。