另一种方法是利用 PostgreSQL 最新版本中的 JSON/JSONB 函数。它的优点是可以处理任何可以转换为 JSON 对象(行或任何其他结构化数据)的东西,而且您甚至不需要知道记录类型。
要找出任何两行/记录之间的差异,您可以使用这个小技巧:
SELECT pre.key AS columname, pre.value AS prevalue, post.value AS postvalue
FROM jsonb_each(to_jsonb(OLD)) AS pre
CROSS JOIN jsonb_each(to_jsonb(NEW)) AS post
WHERE pre.key = post.key AND pre.value IS DISTINCT FROM post.value
其中OLD 和NEW 是在触发器函数中发现的内置记录,分别表示更改记录的前后状态。请注意,我使用了表别名 pre 和 post 而不是 old 和 new 以避免与 OLD 和 NEW 内置对象发生冲突。还要注意使用IS DISTINCT FROM 而不是简单的!= 或<> 来适当地处理NULL 值。
当然,这也适用于任何 ROW 构造函数,例如 ROW(1,2,3,...) 或其简写 (1,2,3,...)。它也适用于任何两个具有相同键的 JSONB 对象。
例如,考虑一个有两行的示例(为了示例的目的,已经转换为 JSONB):
SELECT pre.key AS columname, pre.value AS prevalue, post.value AS postvalue
FROM jsonb_each('{"col1": "same", "col2": "prediff", "col3": 1, "col4": false}') AS pre
CROSS JOIN jsonb_each('{"col1": "same", "col2": "postdiff", "col3": 1, "col4": true}') AS post
WHERE pre.key = post.key AND pre.value IS DISTINCT FROM post.value
查询将显示已更改值的列:
columname | prevalue | postvalue
-----------+-----------+------------
col2 | "prediff" | "postdiff"
col4 | false | true
这种方法很酷的一点是,按列过滤很简单。例如,假设您只想检测列col1 和col2 中的变化:
SELECT pre.key AS columname, pre.value AS prevalue, post.value AS postvalue
FROM jsonb_each('{"col1": "same", "col2": "prediff", "col3": 1, "col4": false}') AS pre
CROSS JOIN jsonb_each('{"col1": "same", "col2": "postdiff", "col3": 1, "col4": true}') AS post
WHERE pre.key = post.key AND pre.value IS DISTINCT FROM post.value
AND pre.key IN ('col1', 'col2')
即使 col3 的值已更改,新结果也会从结果中排除:
columname | prevalue | postvalue
-----------+-----------+------------
col2 | "prediff" | "postdiff"
很容易看出这种方法可以如何以多种方式进行扩展。例如,假设您想在某些列被更新时抛出异常。您可以通过通用触发功能来实现这一点,即可以应用于任何/所有表格的触发功能,而无需知道表格类型:
CREATE OR REPLACE FUNCTION yourschema.yourtriggerfunction()
RETURNS TRIGGER AS
$$
DECLARE
immutable_cols TEXT[] := ARRAY['createdon', 'createdby'];
BEGIN
IF TG_OP = 'UPDATE' AND EXISTS(
SELECT 1
FROM jsonb_each(to_jsonb(OLD)) AS pre, jsonb_each(to_jsonb(NEW)) AS post
WHERE pre.key = post.key AND pre.value IS DISTINCT FROM post.value
AND pre.key = ANY(immutable_cols)
) THEN
RAISE EXCEPTION 'Error 12345 updating table %.%. Cannot alter these immutable cols: %.',
TG_TABLE_SCHEMA, TG_TABLE_NAME, immutable_cols;
END IF;
END
$$
LANGUAGE plpgsql VOLATILE
然后,您可以将上述触发器函数注册到您想要通过以下方式控制的任何和所有表中:
CREATE TRIGGER yourtiggername
BEFORE UPDATE ON yourschema.yourtable
FOR EACH ROW EXECUTE PROCEDURE yourschema.yourtriggerfunction();