ssc*_*ber 3 sql postgresql triggers dynamic-sql plpgsql
给定以下架构:
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;
Run Code Online (Sandbox Code Playgroud)
我尝试在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();
Run Code Online (Sandbox Code Playgroud)
我的不成功的努力是:
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;
Run Code Online (Sandbox Code Playgroud)
问题是update
声明.我无法想出一个可以在这里工作的语法.我已经在PL/Perl中成功实现了这个,但是对plpgsql-only解决方案感兴趣.
有任何想法吗?
更新
正如@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;
Run Code Online (Sandbox Code Playgroud)
虽然@ Gary的答案在技术上是正确的,但他没有提到PostgreSQL 确实支持这种形式:
UPDATE tbl
SET (col1, col2, ...) = (expression1, expression2, ..)
Run Code Online (Sandbox Code Playgroud)
再次阅读本手册UPDATE
.
完成动态SQL的工作仍然很棘手.由于您没有指定,我假设一个简单的情况,其中视图由与其基础表相同的列组成.
CREATE VIEW tbl_view AS SELECT * FROM tbl;
Run Code Online (Sandbox Code Playgroud)
特殊记录NEW
在内部不可见EXECUTE
.我NEW
作为单个参数传递给USING
子句EXECUTE
.
如上所述,UPDATE
list-form需要单独的值.我使用子选择将记录拆分为单独的列:
UPDATE ...
FROM (SELECT ($1).*) x
Run Code Online (Sandbox Code Playgroud)
(括号周围$1
不是可选的.)这允许我简单地使用string_agg()
从目录表构建的两个列列表:一个列表和一个没有表格限定.
无法将行值作为整体分配给各个列.手册:
根据标准,目标列名称的带括号的子列表的源值可以是任何行值表达式,从而产生正确的列数.PostgreSQL只允许源值为行构造函数或子代
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;
Run Code Online (Sandbox Code Playgroud)
触发功能INSERT
:
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;
Run Code Online (Sandbox Code Playgroud)
触发器:
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();
Run Code Online (Sandbox Code Playgroud)
SQL小提琴演示INSERT
和UPDATE
.
包括模式名称以使表引用明确无误.在多个模式中,同一个数据库中可以有多个相同表名的实例!
查询pg_attribute
而不是information_schema.columns
.这是移植性较差,但多快,让我用表OID.
当像SQLI一样处理动态SQL的查询时,表名对SQLi是不安全的.与逃避quote_ident()
或format()
或与对象IDENTIFER类型.这包括特殊的触发函数变量TG_TABLE_SCHEMA
和TG_TABLE_NAME
!
转换为对象标识符类型regclass
以断言表名有效并获取目录查找的OID.
(可选)用于format()
安全地构建动态查询字符串.
对目录表的第一个查询不需要动态SQL.更快,更简单.
除非您知道自己在做什么,否则请使用RETURN NEW
而不是RETURN NULL
在这些触发器功能中.(NULL
将取消INSERT
当前行.)
这个简单的版本假定每个表(和视图)都有一个名为的唯一列id
.更复杂的版本可能会动态使用主键.
该函数UPDATE
允许视图和表的列以任何顺序排列,只要该集合相同即可.INSERT
期望视图和表的列的顺序相同的函数.如果要允许任意顺序,请在INSERT
命令中添加列定义列表,就像使用UPDATE
.
更新版本还包括另外id
使用对列的更改OLD
.