While @Gary的回答 https://stackoverflow.com/a/15343288/939860从技术上讲是正确的,但它没有提到 PostgreSQLdoes支持这种形式:
UPDATE tbl
SET (col1, col2, ...) = (expression1, expression2, ..)
Read 手册上UPDATE https://www.postgresql.org/docs/current/sql-update.html.
使用动态 SQL 来完成此任务仍然很棘手。我将假设一个简单的情况,其中视图由与其基础表相同的列组成。
CREATE VIEW tbl_view AS SELECT * FROM tbl;
Problems
特别记录NEW
里面不可见EXECUTE https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN。我通过NEW
作为单个参数USING
的条款EXECUTE
.
正如所讨论的,UPDATE
以列表形式需要个人values。我使用子选择将记录拆分为单独的列:
UPDATE ...
FROM (SELECT ($1).*) x
(括号内$1
不是可选的。)这使我可以简单地使用使用以下命令构建的两个列列表string_agg()
从目录表来看:一种具有表资格,一种不具有表资格。
无法将行值作为一个整体分配给各个列。手册: https://www.postgresql.org/docs/current/sql-update.html
根据标准,括号中的源值
目标列名称的子列表可以是任何行值表达式
产生正确的列数。 PostgreSQL 只允许
源值是行构造函数或子构造函数SELECT
.
INSERT
实现起来更简单。如果视图和表的结构相同,我们可以省略列定义列表。 (可以改进,见下文。)
Solution
我对你的方法做了一些更新,以使其更加出色。
触发功能为UPDATE
:
CREATE OR REPLACE FUNCTION f_trg_up()
RETURNS TRIGGER
LANGUAGE plpgsql AS
$func$
DECLARE
_tbl regclass := 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
AND NOT attisdropped -- no dropped (dead) columns
AND attnum > 0; -- no system columns
EXECUTE format('
UPDATE %s
SET (%s) = (%s)
FROM (SELECT ($1).*) x', _tbl, _cols, _vals)
USING NEW;
RETURN NEW; -- Don't return NULL unless you knwo what you're doing
END
$func$;
触发功能为INSERT
:
CREATE OR REPLACE FUNCTION f_trg_ins()
RETURNS TRIGGER
LANGUAGE plpgsql AS
$func$
DECLARE
_tbl regclass := quote_ident(TG_TABLE_SCHEMA) || '.'
|| quote_ident(substring(TG_TABLE_NAME FROM '(.+)_view$'));
BEGIN
EXECUTE format('INSERT INTO %s SELECT ($1).*', _tbl)
USING NEW;
RETURN NEW; -- Don't return NULL unless you know what you're doing
END
$func$;
触发器:
CREATE TRIGGER trg_instead_up
INSTEAD OF UPDATE ON a_view
FOR EACH ROW EXECUTE FUNCTION f_trg_up();
CREATE TRIGGER trg_instead_ins
INSTEAD OF INSERT ON a_view
FOR EACH ROW EXECUTE FUNCTION f_trg_ins();
在 Postgres 11 之前,语法(奇怪)是EXECUTE PROCEDURE代替EXECUTE FUNCTION https://dba.stackexchange.com/a/307500/3684- 这也仍然有效。
db<>fiddle - demonstrating INSERT
and UPDATE
Old sqlfiddle http://www.sqlfiddle.com/#!17/4a3a6/1
主要观点
-
包含架构名称以使表引用明确。一个数据库中可以有多个具有多个模式的同名表!
-
Query pg_catalog.pg_attribute
代替information_schema.columns
。可移植性较差,但速度更快,并且允许使用表 OID。
- 如何检查给定模式中是否存在表 https://stackoverflow.com/questions/20582500/how-to-check-if-a-table-exists-in-a-given-schema/24089729#24089729
-
表名不安全SQLi https://en.wikipedia.org/wiki/Sql_injection当连接为动态 SQL 的字符串时。逃脱与quote_ident() or format() https://www.postgresql.org/docs/current/functions-string.html或与对象标识符类型 https://www.postgresql.org/docs/current/datatype-oid.html。这包括特殊触发函数变量TG_TABLE_SCHEMA and TG_TABLE_NAME https://www.postgresql.org/docs/current/plpgsql-trigger.html!
-
投射到对象标识符类型regclass https://www.postgresql.org/docs/current/datatype-oid.html断言表名有效并获取用于目录查找的 OID。
-
可选择使用format() https://www.postgresql.org/docs/current/functions-string.html#FUNCTIONS-STRING-FORMAT安全地构建动态查询字符串。
-
对目录表的第一个查询不需要动态 SQL。更快、更简单。
-
Use RETURN NEW
代替RETURN NULL
在这些触发函数中,除非您知道自己在做什么。 (NULL
将取消INSERT
对于当前行。)
-
这个简单的版本假设每个表(和视图)都有一个名为id
。更复杂的版本可能会动态使用主键。
-
该函数为UPDATE
允许视图和表的列位于任何订单,只要集合是相同的。
该函数为INSERT
期望视图和表的列位于相同的订单。如果要允许任意顺序,请将列定义列表添加到INSERT
命令,就像UPDATE
.
-
更新版本还涵盖了以下更改id
列通过使用OLD
此外。