在触发器函数中,如何获取正在更新的字段

时间:2012-01-06 14:47:25

标签: postgresql triggers sql-update

这可能吗?我有兴趣找出UPDATE请求中指定了哪些列,而不管正在发送的新值可能是也可能不是已存储在数据库中的值。

我想这样做的原因是因为我们有一个表可以从多个来源接收更新。以前,我们没有记录更新源自哪个来源。现在,该表存储了哪些源执行了最新更新。我们可以更改一些来源以发送标识符,但这不是一切的选项。因此,我希望能够识别UPDATE请求何时没有标识符,以便我可以替换为默认值。

4 个答案:

答案 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

其中OLDNEW是在触发器函数中找到的内置记录,分别表示更改后的记录的前和后状态。请注意,我使用表别名prepost而不是oldnew来避免与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

此方法的优点是,按列过滤很简单。例如,假设您只想检测col1col2列中的更改:

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时,不确定是否更好。如果更新频繁以至于存在时间戳相似性的风险,则可以使用序列发生器而不是时间戳。