【问题标题】:INSERT with dynamic table name in trigger function在触发器函数中插入动态表名
【发布时间】:2011-12-16 09:24:25
【问题描述】:

我不确定如何实现以下目标:

CREATE OR REPLACE FUNCTION fnJobQueueBEFORE() RETURNS trigger AS $$
    DECLARE
        shadowname varchar := TG_TABLE_NAME || 'shadow';
    BEGIN
        INSERT INTO shadowname VALUES(OLD.*);
        RETURN OLD;
    END;
$$
LANGUAGE plpgsql;

即将值插入到具有动态生成名称的表中。
执行上面的代码产生:

ERROR:  relation "shadowname" does not exist
LINE 1: INSERT INTO shadowname VALUES(OLD.*)

这似乎表明变量没有被扩展/允许作为表名。我在 Postgres 手册中没有找到对此的参考。

我已经像这样尝试过EXECUTE

  EXECUTE 'INSERT INTO ' || quote_ident(shadowname) || ' VALUES ' || OLD.*;

但没有运气:

ERROR:  syntax error at or near ","
LINE 1: INSERT INTO personenshadow VALUES (1,sven,,,)

RECORD 类型似乎丢失了:OLD.* 似乎被转换为字符串并被重新解析,导致各种类型问题(例如 NULL 值)。

有什么想法吗?

【问题讨论】:

    标签: postgresql triggers dynamic-sql plpgsql


    【解决方案1】:

    PostgreSQL 9.1 或更高版本

    format() 具有转义标识符的内置方法。比以前简单:

    CREATE OR REPLACE FUNCTION foo_before()
      RETURNS trigger AS
    $func$
    BEGIN
       EXECUTE format('INSERT INTO %I.%I SELECT $1.*'
                    , TG_TABLE_SCHEMA, TG_TABLE_NAME || 'shadow')
       USING OLD;
    
       RETURN OLD;
    END
    $func$  LANGUAGE plpgsql;
    

    也适用于 VALUES 表达式。

    db小提琴here
    sqlfiddle.

    要点

    您在几乎成功的版本中收到错误消息,因为OLDEXECUTE不可见。而且,如果您想像尝试的那样连接分解行的各个值,则必须使用 quote_literal() 准备每一列的文本表示,以保证语法有效。您还必须事先知道列名才能处理它们或查询系统目录 - 这与您拥有一个简单的动态触发函数的想法背道而驰...

    我的解决方案避免了所有这些并发症。也简化了一点。

    PostgreSQL 9.0 或更早版本

    format() 尚不可用,所以:

    CREATE OR REPLACE FUNCTION foo_before()
      RETURNS trigger AS
    $func$
    BEGIN
        EXECUTE 'INSERT INTO ' || quote_ident(TG_TABLE_SCHEMA)
                        || '.' || quote_ident(TG_TABLE_NAME || 'shadow')
                        || ' SELECT $1.*'
        USING OLD;
    
        RETURN OLD;
    END
    $func$  LANGUAGE plpgsql;
    

    相关:

    【讨论】:

    • 像魅力一样工作。我想我需要进一步阅读:) 不过,您的manual link 中有一个错字。无法更正,因为 stackoverflow 认为一个字符编辑太小了:/
    • @Johan:啊,不知道。所以,我得继续问这么好的问题g
    • @ErwinBrandstetter :我的模式和表名都是大写的。因此,在执行此查询时,由于双引号 EXECUTE format ('INSERT INTO %I SELECT $1.*', 'SCHEMA_NAME' || '.' || TG_TABLE_NAME || '_SHADOW') USING (row) 放错位置而出现错误。错误是:ERROR: relation "SCHEMA_NAME.TABLE_NAME" does not exist
    • @Siddharth:架构包含在单独的变量 TG_TABLE_SCHEMA 中。似乎您在正确转义之前连接了两个 before。考虑:stackoverflow.com/a/10711349/939860。如果问题仍然不清楚,请开始一个新问题。您可以随时参考此答案的上下文和/或添加评论,链接到相关问题。
    • @Jian: USINGEXECUTE 语法的一部分。
    【解决方案2】:

    我只是偶然发现了这个,因为我正在寻找一个动态的INSTEAD OF DELETE 触发器。为了感谢您的提问和回答,我将发布我的 Postgres 9.3 解决方案。

    CREATE OR REPLACE FUNCTION set_deleted_instead_of_delete()
    RETURNS TRIGGER AS $$
    BEGIN
        EXECUTE format('UPDATE %I set deleted = now() WHERE id = $1.id', TG_TABLE_NAME)
        USING OLD;
        RETURN NULL;
    END;
    $$ language plpgsql;
    

    【讨论】:

    • 您能否解释一下,您的解决方案带来了哪些新概念或想法?
    • 再次阅读我的帖子,我发现我并不清楚它的上下文。我的帖子是针对正在搜索动态删除触发器功能的帖子。当 Google 将此问题显示为热门问题时,我正在搜索此问题。
    • 本来想在答案下发表评论,但是代码高亮不起作用
    • 重要的是要注意这仅适用于INSTEAD OF DELETE 触发器。我冒昧地作出相应的澄清。
    猜你喜欢
    • 2021-12-21
    • 2019-11-15
    • 2021-06-09
    • 1970-01-01
    • 2013-05-30
    • 2011-01-06
    • 1970-01-01
    • 2014-06-08
    • 1970-01-01
    相关资源
    最近更新 更多