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

2024-06-01

给出以下架构:

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;

我尝试创建一个genericplpgsql 中的触发器函数,可以更新视图:

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 的解决方案感兴趣。
有任何想法吗?

Update

正如 @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;

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此外。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

更新plpgsql中触发器函数中的多列 的相关文章

  • 是否可以在 Hibernate 中使用分析函数?

    有没有办法在 Hibernate 中使用类似 sql server 的分析函数 就像是 select foo from Foo foo where f x max f x over partition by f y 您正在寻找本机 SQL
  • pgadmin:收到“详细信息:用户没有 CONNECT 权限。”错误

    我在 Mac Yosemite 上使用 pgAdmin III 我创建了一个角色 discount 和一个数据库 discount 在 pgadmin 工具中 如何授予用户 discount 对数据库 discount 的连接权限 以及表读
  • 当 SQL 包含变量时在 pgAdmin 中调试 SQL

    在 SQL Server 中 我可以从应用程序中复制 sql 代码并将其粘贴到 SSMS 中 声明并分配 sql 中存在的变量并运行 是的 很棒的调试场景 例如 请注意 我很生疏 语法可能不正确 declare x as varchar 1
  • 减少 plpgsql 中烦人的通知

    我有一个使用临时表的函数 如果存在则必须将其删除 drop table if exists t xy create temp table t xy on commit drop as select 随后我在视图中使用这个函数 当 selec
  • 将记录与另一个表上的最新记录连接

    我正在尝试创建一个 SQL 视图 我如何从一个表中选择最新的记录 而其他记录保持原样 我需要从所有表中选择所有记录 这工作正常 但我需要仅按日期选择最新的提案 这是我遇到的问题 这是我到目前为止所拥有的 SELECT TOP 100 PER
  • 选择语句REF oracle

    我需要一些帮助来创建将使用引用的选择语句 我设法很好地插入了值 但是当我尝试使用 where 语句提取值时 输出要么是数据类型错误 要么会输出两个表以及它们都包含的数据 这只是一个例子 Create or replace table1 Ty
  • 与 Postgres 的 TCP 连接安全吗?需要 SSL 吗?

    早上好 我正在浏览 Postgresql 配置文件 最近注意到有一个ssl选项 我想知道什么时候需要这样做 假设您有一个应用程序服务器和一个数据库服务器 不在专用网络内运行 如果用户尝试登录 如果未启用 SSL 应用程序服务器在查找用户密码
  • 在数据库中有效存储商品位置(用于订购)

    设想 有一个用户拥有的电影数据库 电影显示在一个名为 我的电影 的页面上 电影可以按照用户想要的顺序显示 例如 位置 1 为 搏击俱乐部 位置 3 为 Drive 依此类推 显而易见的解决方案是存储每个项目的位置 例如 电影 ID 用户 I
  • 如何从 tarantool 中选择有限数量的记录,就像 SQL 中的 SELECT LIMIT 一样?

    我想在 Tarantool 空间上执行选择 使用过滤和限制结果 就像我可以使用简单的 SQL 查询一样 SELECT FROM users WHERE age gt 33 LIMIT 1 我怎样才能做到这一点 可以使用 Lua 和 SQL
  • Oracle 函数编译成功,但在执行 PLS-00221 时抛出错误:不是过程或未定义

    我有简单的oracle功能 create or replace function abs test func test in in number return number is test out number BEGIN test out
  • 排除任何字段中具有 NULL 值的行结果?

    我有一个像这样的简单选择 SELECT FROM table WHERE fk id 10020 它可以工作 但有一些字段为 NULL 没有模式所以做了 SELECT FROM table WHERE fk id 10020 AND NOT
  • 基准测试:PostgreSQL 上的 bigint 与 int

    我想提高数据库性能 在一个项目中 所有表都来自int to bigint 我认为这不仅在存储方面是一个糟糕的选择 因为int需要4 bytes and bigint需要8 bytes 但也与性能有关 所以我创建了一个小表1000万条目 其中
  • 如何在 WPF 中更改按钮 MouseOver 的背景?

    我的页面上有一个带有以下 XAML 的按钮
  • 我应该如何优化 .net 代码中对一个简单存储过程的多次调用?

    我有一个非常简单的存储过程 create procedure spFoo v varchar 50 as insert into tbFoo select v 我有 50 个值要插入到 tbFoo 中 这意味着在我的 c 代码中我调用 sp
  • Brew Postgresql 启动但进程未运行

    我在 Mac 上通过 Brew 安装了 Postgres 然后 我尝试启动它 gt brew services restart postgres Stopping postgresql might take a while gt Succe
  • SQL Server:比较两个表中的列

    我最近完成了从某些应用程序的旧版本到当前版本的迁移 在迁移数据库时遇到了一些问题 我需要一个可以帮助我比较两个表中的列的查询 我的意思不是行中的数据 我需要比较列本身来弄清楚我错过了表结构的哪些变化 看一下红门 SQL 比较 http ww
  • 获取SQL中前2个特殊字符之间的字符

    我有数据在sql 只是要注意 SQL STudio is the IDE like data a 10 b c a 1 b c 我想获取前两个符号之间的数据 Output 10 1 这就是我的方法 SELECT CAST
  • 对时间序列数据重新采样

    我有一个以毫秒为单位的时间序列列表 我想对时间序列进行重新采样并对组应用平均值 我如何在 Postgres 中实现它 重新采样 是指聚合一秒或一分钟内的所有时间戳 一秒或一分钟内的所有行形成一组 表结构 date x y z Use dat
  • 批量更新 SQL Server C#

    我有一个 270k 行的数据库 带有主键mid和一个名为value 我有一个包含中值和值的文本文件 现在我想更新表格 以便将每个值分配给正确的中间值 我当前的方法是从 C 读取文本文件 并为我读取的每一行更新表中的一行 必须有更快的方法来做
  • 更新查询时 ios 中出现“数据库锁定”错误

    我正在使用下面的代码更新查询 using sqlite 但我越来越 database is locked error 我尝试搜索一些 SO 链接 建议关闭数据库 但我再次执行此操作时遇到相同的错误 我已经提到过代码中出现错误的地方 cons

随机推荐