这可能吗?我有兴趣找出UPDATE
请求中指定了哪些列,而不管正在发送的新值可能是也可能不是已存储在数据库中的值。
我想这样做的原因是因为我们有一个表可以从多个来源接收更新。以前,我们没有记录更新源自哪个来源。现在,该表存储了哪些源执行了最新更新。我们可以更改一些来源以发送标识符,但这不是一切的选项。因此,我希望能够识别UPDATE
请求何时没有标识符,以便我可以替换为默认值。
答案 0 :(得分:21)
如果"来源"没有发送标识符",列将保持不变。然后,您无法检测当前UPDATE
是否由与最后一个源相同的源完成,还是由根本没有更改列的源完成。换句话说:这不能正常工作。
如果"来源"可由任何session information function识别,您可以使用它。像:
NEW.column = session_user;
无条件地进行每次更新。
我找到了解决原始问题的方法。该列将在任何更新中设置为默认值,其中列未更新(不在SET
的{{1}}列表中)
关键元素是PostgreSQL 9.0中引入的per-column trigger - 使用UPDATE
UPDATE OF
子句的特定于列的触发器。
只有至少有一个列出的列时,触发器才会触发 被提及为
column_name
命令的目标。
这是我发现的唯一一种简单方法,可以区分列是否使用与旧版本相同的新值进行更新,而不是更新。
一个可以也解析current_query()
返回的文本。但这似乎很棘手且不可靠。
我假设列UPDATE
定义为col
。
第1步:如果未更改,请将NOT NULL
设置为col
:
NULL
第2步:恢复旧值。如果值实际更新,触发器将仅触发(见下文):
CREATE OR REPLACE FUNCTION trg_tbl_upbef_step1()
RETURNS trigger AS
$func$
BEGIN
IF OLD.col = NEW.col THEN
NEW.col := NULL; -- "impossible" value
END IF;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
第3步:现在我们可以识别缺少的更新并改为设置默认值:
CREATE OR REPLACE FUNCTION trg_tbl_upbef_step2()
RETURNS trigger AS
$func$
BEGIN
IF NEW.col IS NULL THEN
NEW.col := OLD.col;
END IF;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
每列会触发第2步的触发器!
CREATE OR REPLACE FUNCTION trg_tbl_upbef_step3()
RETURNS trigger AS
$func$
BEGIN
IF NEW.col IS NULL THEN
NEW.col := 'default value';
END IF;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
触发器名称是相关的,因为它们按字母顺序触发(全部为CREATE TRIGGER upbef_step1
BEFORE UPDATE ON tbl
FOR EACH ROW
EXECUTE PROCEDURE trg_tbl_upbef_step1();
CREATE TRIGGER upbef_step2
BEFORE UPDATE OF col ON tbl -- key element!
FOR EACH ROW
EXECUTE PROCEDURE trg_tbl_upbef_step2();
CREATE TRIGGER upbef_step3
BEFORE UPDATE ON tbl
FOR EACH ROW
EXECUTE PROCEDURE trg_tbl_upbef_step3();
)!
这个过程可以简化为" per-not-column triggers"或以任何其他方式检查触发器中BEFORE UPDATE
的目标列表。但我认为没有办法解决这个问题。
如果UPDATE
可以是col
,请使用其他任何"不可能的"中间值并在触发函数1中另外检查NULL
:
NULL
相应调整其余部分。
答案 1 :(得分:8)
另一种方法是利用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_json(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();
答案 2 :(得分:2)
在plpgsql中,您可以在触发器函数中执行以下操作:
IF NEW.column IS NULL THEN
NEW.column = 'default value';
END IF;
答案 3 :(得分:0)
我几乎很自然地获得了类似问题的另一种解决方案,因为我的表包含一个语义为&#39;最后更新时间戳的列。 (我们称之为UPDT)。
因此,我决定只在一次更新中包含源和UPDT的新值(或者不包含任何更新)。由于UPDT旨在更改每次更新,因此可以使用条件new.UPDT = old.UPDT
推断出当前更新未指定任何来源并替换默认值。
如果已经有“最后更新时间戳”&#39;在他的表中,这个解决方案比创建三个触发器更简单。在不需要创建UPDT时,不确定是否更好。如果更新频繁以至于存在时间戳相似性的风险,则可以使用序列发生器而不是时间戳。