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

Evi*_*t7x 27 postgresql triggers sql-update

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

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

Erw*_*ter 23

如果"源"不"发送标识符",则列将保持不变.然后,您无法检测当前UPDATE是由与最后一个源相同的源还是由根本未更改列的源完成.换句话说:这不能正常工作.

如果"源"可由任何会话信息功能识别,则可以使用该功能.喜欢:

NEW.column = session_user;
Run Code Online (Sandbox Code Playgroud)

每次更新都无条件.

一般解决方案

我找到了解决原始问题的方法.在未更新列的任何更新中(不在列表中),该列将设置为默认值.SETUPDATE

关键元素是PostgreSQL 9.0中引入的每列触发器 - 使用该UPDATE OFcolumn_name子句的特定于列的触发器.

只有在列出至少一个列出的列作为UPDATE命令的目标时,触发器才会触发.

这是我发现的唯一一种简单方法,可以区分列是否使用与旧值相同的新值进行更新,而不是更新.

一个可能还通过解析返回的文本current_query().但这似乎很棘手且不可靠.

触发功能

我假设一个列已col定义NOT NULL.

第1步:设置colNULL如果不变:

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;
Run Code Online (Sandbox Code Playgroud)

第2步:恢复旧值.只有在实际更新了值时才会触发触发器(见下文):

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;
Run Code Online (Sandbox Code Playgroud)

第3步:现在我们可以识别缺少的更新并设置默认值:

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;
Run Code Online (Sandbox Code Playgroud)

触发器

每列触发步骤2的触发器!

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();
Run Code Online (Sandbox Code Playgroud)

触发器名称是相关的,因为它们按字母顺序触发(全部都是BEFORE UPDATE)!

可以使用"per-not-column triggers"或任何其他方式来简化程序,以检查触发器中的目标列表UPDATE.但我认为没有办法解决这个问题.

如果col可以NULL,使用任何其他"不可能"的中间值并NULL在触发功能1中另外检查:

IF OLD.col IS NOT DISTINCT FROM NEW.col THEN
    NEW.col := '#impossible_value#';
END IF;
Run Code Online (Sandbox Code Playgroud)

相应调整其余部分.

  • 我只后悔我对这个答案只有一个赞成票。 (2认同)

Dem*_*nez 16

另一种方法是利用最新版本的 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
Run Code Online (Sandbox Code Playgroud)

哪里OLDNEW是在触发器函数中找到的内置记录,分别代表更改记录的前后状态。请注意,我使用了表别名preandpost而不是oldandnew以避免与 OLD 和 NEW 内置对象发生冲突。还要注意使用 ofIS DISTINCT FROM而不是简单的!=or<>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
Run Code Online (Sandbox Code Playgroud)

查询将显示已更改值的列:

 columname | prevalue  | postvalue
-----------+-----------+------------
 col2      | "prediff" | "postdiff"
 col4      | false     | true
Run Code Online (Sandbox Code Playgroud)

这种方法很酷的一点是按列过滤很简单。例如,假设您只想检测列中的更改,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')
Run Code Online (Sandbox Code Playgroud)

col3即使新结果的值发生了变化,它也会从结果中排除:

 columname | prevalue  | postvalue
-----------+-----------+------------
 col2      | "prediff" | "postdiff"
Run Code Online (Sandbox Code Playgroud)

很容易看出如何以多种方式扩展这种方法。例如,假设您想在某些列更新时抛出异常。您可以使用通用触发器函数来实现这一点,即可以应用于任何/所有表的函数,而无需知道表类型:

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
Run Code Online (Sandbox Code Playgroud)

然后,您可以通过以下方式将上述触发器函数注册到您想要控制的任何和所有表:

CREATE TRIGGER yourtiggername
BEFORE UPDATE ON yourschema.yourtable
FOR EACH ROW EXECUTE PROCEDURE yourschema.yourtriggerfunction();
Run Code Online (Sandbox Code Playgroud)