更新后显示新列值的 Postgres 函数为 JSON
Postgres function to show new column values after update as JSON
我正在尝试为 AFTER UPDATE
触发器编写一个 postgres 函数,该函数创建一个 JSON 对象,其中包含列名称及其仅针对已更改列的更新值。我正在尝试创建一个可以用于任何 table 的通用函数,因此使用 Record
作为函数参数。 old
和 new
来自触发器的 OLD
和 NEW
变量。最终目标是将返回的 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 INSERT
和 AFTER 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
类型的函数参数。
我正在尝试为 AFTER UPDATE
触发器编写一个 postgres 函数,该函数创建一个 JSON 对象,其中包含列名称及其仅针对已更改列的更新值。我正在尝试创建一个可以用于任何 table 的通用函数,因此使用 Record
作为函数参数。 old
和 new
来自触发器的 OLD
和 NEW
变量。最终目标是将返回的 JSON 保存到审核 table.
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 INSERT
和 AFTER 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
类型的函数参数。