【问题标题】:PostgreSQL modifying fields dynamically in NEW record in a trigger functionPostgreSQL在触发器函数中动态修改新记录中的字段
【发布时间】:2015-03-17 00:17:04
【问题描述】:

我有一个包含 ID 和用户名(以及其他详细信息)的用户表,还有几个其他表使用各种列名 (CONSTRAINT some_name FOREIGN KEY (columnname) REFERENCES "user" (userid)) 引用此表。我需要做的是将用户名添加到引用表中(准备删除整个用户表)。这当然可以通过单个ALTER TABLEUPDATE 轻松完成,并且使用触发器使这些保持最新也(相当)容易。但让我有些烦恼的是触发功能。我可以为每个表使用单独的函数,但这似乎是多余的,因此我为此创建了一个通用函数:

CREATE OR REPLACE FUNCTION public.add_username() RETURNS trigger AS
$BODY$
  DECLARE
    sourcefield text;
    targetfield text;
    username text;
    existing text;
  BEGIN
    IF (TG_NARGS != 2) THEN
      RAISE EXCEPTION 'Need source field and target field parameters';
    END IF;
    sourcefield = TG_ARGV[0];
    targetfield = TG_ARGV[1];
    EXECUTE 'SELECT username FROM "user" WHERE userid = ($1).' || sourcefield INTO username USING NEW;
    EXECUTE format('SELECT ($1).%I', targetfield) INTO existing USING NEW;
    IF ((TG_OP = 'INSERT' AND existing IS NULL) OR (TG_OP = 'UPDATE' AND (existing IS NULL OR username != existing))) THEN
      CASE targetfield
        WHEN 'username' THEN
          NEW.username := username;
        WHEN 'modifiername' THEN
          NEW.modifiername := username;
        WHEN 'creatorname' THEN
          NEW.creatorname := username;
        .....
      END CASE;
    END IF;
    RETURN NEW;
  END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;

并使用触发功能:

CREATE TRIGGER some_trigger_name BEFORE UPDATE OR INSERT ON my_schema.my_table FOR EACH ROW EXECUTE PROCEDURE public.add_username('userid', 'username');

其工作方式是触发函数通过 TG_ARGV 接收原始源字段名称(例如 userid)和目标字段名称(username)。然后这些用于填写(可能)缺失的信息。所有这一切都很好,但我怎样才能摆脱CASE-mess?当我事先不知道字段的名称(或者更确切地说可能是很多东西)时,有没有办法动态修改 NEW 记录中的值?它在targetfield 参数中,但显然NEW.targetfield 不起作用,也不是NEW[targetfield] 之类的东西(例如Javascript)。

任何想法如何实现?除了使用例如 PL/Python..

【问题讨论】:

标签: postgresql plpgsql


【解决方案1】:

没有简单的基于 plpgsql 的解决方案。一些可能的解决方案:

  1. 使用hstore 扩展。
CREATE TYPE footype AS (a int, b int, c int); postgres=# 选择行(10,20,30); 排 ------------ (10,20,30) (1 行) postgres=# 选择行(10,20,30)::footype #= 'b=>100'; ?柱子? ------------- (10,100,30) (1 行)

基于hstore的函数可以很简单:

创建或替换函数 update_fields(r anyelement, 可变参数更改文本 []) 将任何元素返回为 $$ 选择 $1 #= hstore($2); $$语言sql; postgres=# 选择 * 从 update_fields(row(10,20,30)::footype, 'b', '1000', 'c', '800'); 一个 |乙 | C ----+------+----- 10 | 1000 | 800 (1 行)
  1. 几年前我写了一个扩展pl toolbox。有一个函数record_set_fields
pavel=# select * from pst.record_expand(pst.record_set_fields(row(10,20),'f1',33)); 姓名 |价值 |典型 ------+--------+---------- f1 | 33 |整数 f2 | 20 |整数 (2 行)

也许你可以找到一些基于系统表和数组(如this)的技巧的仅限 plpgsql 的解决方案,但我不能建议它。它的可读性太差,对于不高级的用户来说只是黑魔法。 hstore 很简单,几乎无处不在,所以它应该是首选方式。

在 PostgreSQL 9.4(可能是 9.3)上,您可以尝试使用 JSON 操作来施展魔法:

postgres=# 选择 json_populate_record(NULL::footype, jo) 从(选择json_object(array_agg(key), array_agg('b' 时的大小写键 然后 1000::text 其他值 结束))乔 从 json_each_text(row_to_json(row(10,20,30)::footype))) x; json_populate_record ---------------------- (10,1000,30) (1 行)

所以我可以写函数:

创建或替换函数 public.update_field(r anyelement, fn 文本,val 文本, OUT 结果任何元素) 返回任何元素 语言 plpgsql 作为$函数$ 声明 jo json; 开始 jo := (选择 json_object(array_agg(key), array_agg('b' 时的大小写键,然后 val 否则价值结束)) 从 json_each_text(row_to_json(r))); 结果 := json_populate_record(r, jo); 结尾; $函数$ postgres=# select * from update_field(row(10,20,30)::footype, 'b', '1000'); 一个 |乙 | C --+--------+---- 10 | 1000 | 30 (1 行)

基于 JSON 的函数应该不会太快。 hstore 应该更快。

【讨论】:

  • 我也想过尝试 JSON,但你是对的,它可能不是那么快,hstore 也是我的第二选择,但我想我会要求一个“纯“先解决。看来我别无选择,只能这样。感谢您的帮助。
  • 基于 json 的 update_field 函数中存在硬编码。对于“case key when 'b' then”,其中 'b' 应替换为 fn。我无法直接改进您的答案(更改少于 6 个字符需要 2000 分)
【解决方案2】:

更新/警告: Erwin 指出这目前没有记录,文档表明不应该以这种方式更改记录。 使用Pavel's solution 或 hstore。

简化后,基于 json 的解决方案几乎与 hstore 一样快。 json_populate_record() 为我们修改现有记录,因此我们只需要根据我们想要更改的键创建一个 json 对象。

查看我的similar answer,您可以在其中找到比较解决方案的基准。

最简单的解决方案需要 Postgres 9.4

SELECT json_populate_record (
      record
     ,json_build_object('key', 'new-value')
);

但是如果你只有 Postgres 9.3,你可以使用 cast 代替 json_object:

SELECT json_populate_record( 
     record
    , ('{"'||'key'||'":"'||'new-value'||'"}')::json
);

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2019-07-06
  • 1970-01-01
  • 1970-01-01
  • 2018-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多