PostgreSQL在触发器函数中的NEW记录中动态修改字段

时间:2015-02-02 11:53:07

标签: postgresql plpgsql

我有一个包含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 - 混乱?有没有办法动态修改NEW记录中的值,因为我事先不知道字段的名称(或者说它可能是很多东西)?它位于targetfield参数中,但显然NEW.targetfield不起作用,也不像NEW[targetfield](例如Javascript)。

有关如何实现这一目标的任何想法?除了使用例如PL / Python ..

2 个答案:

答案 0 :(得分:3)

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

  1. 使用hstore扩展名。
  2. CREATE TYPE footype AS (a int, b int, c int);
    
    postgres=# select row(10,20,30);
        row     
    ------------
     (10,20,30)
    (1 row)
    
    postgres=# select row(10,20,30)::footype #= 'b=>100';
      ?column?   
    -------------
     (10,100,30)
    (1 row)
    
    基于{p> hstore的函数可以非常简单:

    create or replace function update_fields(r anyelement,
                                             variadic changes text[])
    returns anyelement as $$
    select $1 #= hstore($2);
    $$ language sql;
    
    postgres=# select * 
                 from update_fields(row(10,20,30)::footype, 
                                    'b', '1000', 'c', '800');
     a  |  b   |  c  
    ----+------+-----
     10 | 1000 | 800
    (1 row)
    
    1. 几年前我写了一篇pl toolbox。有一个函数record_set_fields
    2. 
      pavel=# select * from pst.record_expand(pst.record_set_fields(row(10,20),'f1',33));
       name | value |   typ   
      ------+-------+---------
       f1   | 33    | integer
       f2   | 20    | integer
      (2 rows)
      

      可能你可以找到一些基于plpgsql的解决方案,这些解决方案基于系统表和this等数组的一些技巧,但我无法提出建议。它的可读性太低,而不是高级用户只是黑魔法。 hstore很简单,几乎无处不在,所以它应该是首选方式。

      在PostgreSQL 9.4(可能是9.3)上,您可以尝试使用JSON操作进行黑魔术:

      postgres=# select json_populate_record(NULL::footype, jo) 
                    from (select json_object(array_agg(key),
                                             array_agg(case key when 'b' 
                                                                then 1000::text
                                                                else value 
                                                       end)) jo
             from json_each_text(row_to_json(row(10,20,30)::footype))) x;
       json_populate_record 
      ----------------------
       (10,1000,30)
      (1 row)
      

      所以我能写函数:

      CREATE OR REPLACE FUNCTION public.update_field(r anyelement, 
                                                     fn text, val text, 
                                                     OUT result anyelement)
       RETURNS anyelement
       LANGUAGE plpgsql
      AS $function$
      declare jo json;
      begin
        jo := (select json_object(array_agg(key), 
                                  array_agg(case key when 'b' then val
                                                     else value end)) 
                  from json_each_text(row_to_json(r)));
        result := json_populate_record(r, jo);
      end;
      $function$
      
      postgres=# select * from update_field(row(10,20,30)::footype, 'b', '1000');
       a  |  b   | c  
      ----+------+----
       10 | 1000 | 30
      (1 row)
      

      基于JSON的功能应该不会很快。 hstore应该更快。

答案 1 :(得分:1)

更新/警告: 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
);