更新plpgsql中触发器函数中的多个列

时间:2013-03-11 15:58:34

标签: sql postgresql triggers plpgsql dynamic-sql

给出以下架构:

create table account_type_a (
  id SERIAL UNIQUE PRIMARY KEY,
  some_column VARCHAR
);

create table account_type_b (
  id SERIAL UNIQUE PRIMARY KEY,
  some_other_column VARCHAR
);

create view account_type_a view AS select * from account_type_a;
create view account_type_b view AS select * from account_type_b;

我尝试在plpgsql中创建一个泛型触发器函数,它可以更新视图:

create trigger trUpdate instead of UPDATE on account_view_type_a
for each row execute procedure updateAccount();    
create trigger trUpdate instead of UPDATE on account_view_type_a
for each row execute procedure updateAccount();

我的努力失败了:

create function updateAccount() returns trigger as $$
declare
  target_table varchar := substring(TG_TABLE_NAME from '(.+)_view');
  cols varchar;
begin
  execute 'select string_agg(column_name,$1) from information_schema.columns
           where table_name = $2' using ',', target_table into cols;
  execute 'update ' || target_table || ' set (' || cols || ') =  select ($1).*
           where id = ($1).id' using NEW;
  return NULL;
end;
$$ language plpgsql;

问题是update声明。我无法想出一个可以在这里工作的语法。我已经在PL / Perl中成功实现了这个,但是对于仅支持plpgsql的解决方案感兴趣 有什么想法吗?

更新

正如@Erwin Brandstetter建议的那样,这是我的PL / Perl解决方案的代码。我提出了他的一些建议。

create function f_tr_up() returns trigger as $$
  use strict;
  use warnings;
  my $target_table = quote_ident($_TD->{'table_name'}) =~ s/^([\w]+)_view$/$1/r;
  my $NEW = $_TD->{'new'};
  my $cols = join(',', map { quote_ident($_) } keys $NEW);
  my $vals = join(',', map { quote_literal($_) } values $NEW);
  my $query = sprintf(
    "update %s set (%s) = (%s) where id = %d",
    $target_table,
    $cols,
    $vals,
    $NEW->{'id'});
  spi_exec_query($query);
return;
$$ language plperl;

2 个答案:

答案 0 :(得分:8)

虽然@Gary's answer在技术上是正确的,但他没有提到PostgreSQL 支持支持这种形式:

UPDATE tbl
SET (col1, col2, ...) = (expression1, expression2, ..)

再次阅读the manual on UPDATE

完成动态SQL的工作仍然很棘手。由于您没有指定,我假设一个简单的情况,其中视图由与其基础表相同的列组成。

CREATE VIEW tbl_view AS SELECT * FROM tbl;

问题

  • EXECUTE内看不到特殊记录NEW。我将NEW作为单个参数传递给USING的{​​{1}}子句。

  • 如上所述,带有list-form的EXECUTE需要单独的。我使用子选择将记录拆分为单独的列:

    UPDATE

    UPDATE ... FROM (SELECT ($1).*) x 左右的括号不是可选的。)这允许我简单地使用目录表中使用$1构建的两个列列表:一个有一个没有表限定。

    < / LI>
  • 无法将行值作为整体分配给各个列。 The manual:

      

    根据标准,括号内的源值   目标列名的子列表可以是任何行值表达式   产生正确的列数。 PostgreSQL只允许   源值是行构造函数或子{ - 1}}。

  • string_agg()实现得更简单。假设视图和表的结构相同,我省略了列定义列表。 (可以改进,见下文。)

解决方案

我对你的方法进行了一些更新,以使它发光。

SELECT的触发功能:

INSERT

UPDATE的触发功能:

CREATE OR REPLACE FUNCTION f_trg_up()
  RETURNS TRIGGER AS
$func$
DECLARE
   tbl  text := quote_ident(TG_TABLE_SCHEMA) || '.'
             || quote_ident(substring(TG_TABLE_NAME from '(.+)_view$'));
   cols text;
   vals text;
BEGIN
   SELECT INTO cols, vals
          string_agg(quote_ident(attname), ', ')
         ,string_agg('x.' || quote_ident(attname), ', ')
   FROM   pg_attribute
   WHERE  attrelid = tbl::regclass
   AND    NOT attisdropped   -- no dropped (dead) columns
   AND    attnum > 0;        -- no system columns

   EXECUTE format('
   UPDATE %s t
   SET   (%s) = (%s)
   FROM  (SELECT ($1).*) x
   WHERE  t.id = ($2).id'
   , tbl, cols, vals) -- assuming unique "id" in every table
   USING NEW, OLD;

   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

触发器:

INSERT

SQL Fiddle演示CREATE OR REPLACE FUNCTION f_trg_ins() RETURNS TRIGGER AS $func$ DECLARE tbl text := quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(substring(TG_TABLE_NAME from '(.+)_view$')); BEGIN EXECUTE 'INSERT INTO ' || tbl || ' SELECT ($1).*' USING NEW; RETURN NEW; -- don't return NULL unless you know what you're doing END $func$ LANGUAGE plpgsql; CREATE TRIGGER trg_instead_up INSTEAD OF UPDATE ON a_view FOR EACH ROW EXECUTE PROCEDURE f_trg_up(); CREATE TRIGGER trg_instead_ins INSTEAD OF INSERT ON a_view FOR EACH ROW EXECUTE PROCEDURE f_trg_ins();

重点

  • 包含模式名称以使表引用明确无误。在多个模式中,同一个数据库中可以有多个相同表名的实例!

  • 查询INSERT而不是UPDATE。这样便携性较差,但很多更快,并且允许我使用table-OID。

  • 在为动态SQL构建查询时,表格名称对于SQLi 是不安全的。使用quote_ident() or format()object-identifer type逃生。这包括special trigger function variables TG_TABLE_SCHEMA and TG_TABLE_NAME

  • 转换为object identifier type regclass以断言表名有效并获取目录查找的OID。

  • 可选择使用format()安全地构建动态查询字符串。

  • 对目录表的第一个查询不需要动态SQL。更快,更简单。

  • 在这些触发器功能中使用pg_attribute而不是information_schema.columns,除非您知道自己在做什么。 (RETURN NEW会取消当前行的RETURN NULL。)

  • 这个简单版本假设每个表(和视图)都有一个名为NULL的唯一列。更复杂的版本可能会动态使用主键。

  • INSERT的函数允许视图和表的列在任何顺序中,只要该集合相同即可。 id的函数要求视图和表的列以相同的顺序。如果要允许任意顺序,请在UPDATE命令中添加列定义列表,就像使用INSERT一样。

  • 更新版本还涵盖了INSERT列的更改,另外还使用了UPDATE

答案 1 :(得分:1)

Postgresql不支持使用set (col1,col2) = select val1,val2语法更新多个列。

要在postgresql中实现相同的功能,请使用

update target_table
set col1 = d.val1,
    col2 = d.val2
from source_table d
where d.id = target_table.id

这将使动态查询构建起来有点复杂,因为您需要将您正在使用的列名列表迭代到单个字段中。我建议您使用array_agg而不是string_agg,因为数组比再次拆分字符串更容易处理。

Postgresql UPDATE syntax

documentation on array_agg function