给出以下架构:
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;
答案 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
构建的两个列列表:一个有一个没有表限定。
无法将行值作为整体分配给各个列。 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
,因为数组比再次拆分字符串更容易处理。