使用PL/pgsql中的EXECUTE从通用触发器插入NEW.*

Adr*_*onk 15 postgresql triggers plpgsql

我有许多使用Postgres"Partitioning"功能的表.我想在每个表上定义一个常见的BEFORE INSERT OF ROW触发器,1)如果针对父表发生插入,则动态创建分区; 2)对分区重新执行插入.

就像是:

CREATE OR REPLACE FUNCTION partition_insert_redirect( )
RETURNS trigger AS $BODY$
BEGIN
  ... create the new partition and set up the redirect Rules ...

  /* Redo the INSERT dynamically.  The new RULE will redirect it to the child table */
  EXECUTE 'INSERT INTO ' || quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME) ||
          ' SELECT NEW.*'
END
Run Code Online (Sandbox Code Playgroud)

但是在EXECUTE SQL中看不到"NEW"记录.我怎样才能尽可能简单地完成这项工作?

作为替代方案,我可以以某种方式迭代新记录中的字段吗?

我想过使用临时表:

EXECUTE 'CREATE TEMPORARY TABLE new_row (LIKE ' ||
        quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME) ||
        ') ON COMMIT DROP';

INSERT INTO new_row SELECT NEW.*;

EXECUTE 'INSERT INTO ' || quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(TG_TABLE_NAME) ||
       ' SELECT * FROM new_row';
DROP TABLE new_row;
Run Code Online (Sandbox Code Playgroud)

但是由于对temp-table的缓存引用,这也行不通:当访问PL/PgSQL函数中的临时表时,为什么会出现"与OID的关系#####不存在"错误?

我正在使用Postgres 8.2,我无法更改为任何其他版本.

编辑:
正如@alvherre所指出的,这可能是在Postgres 8.4中使用EXECUTE ... USING语法完成的.请参阅http://wiki.postgresql.org/wiki/PL/pgSQL_Dynamic_Triggers上的示例

alv*_*rre 22

您可以使用EXECUTE USING将NEW传递给它.你的例子就是

EXECUTE 'INSERT INTO ' || TG_RELID || '::regclass SELECT $1' USING NEW;
Run Code Online (Sandbox Code Playgroud)

(注意我使用TG_RELID来进行regclass而不是摆弄TG_TABLE_SCHEMA和TABLE_NAME,因为它更容易使用,如果非标准的话.但是,无论如何,plpgsql都是非标准的.)

  • 如果使用表名而不是relid,'select $ 1'应为'select $ 1.*' (2认同)

Adr*_*onk 1

我已经设法通过动态编译一个接受新行作为参数的函数来使其工作:

    EXECUTE 'create or replace function partition_insert(r ' || TG_TABLE_NAME || ') RETURNS void AS $FUNC$' || 
            'BEGIN ' ||
                'insert into ' || TG_TABLE_NAME || ' SELECT r.*; ' ||
            'END $FUNC$ LANGUAGE plpgsql VOLATILE';
    PERFORM partition_insert(NEW);
Run Code Online (Sandbox Code Playgroud)

由于 Postgres 函数是多态的,这将为使用此触发器的每个表生成不同的函数。

尽管是一个丑陋的拼凑,但这似乎可以完成工作。

尽管看起来我可以在构建系统时预先定义每个多态变体,但由于缓存的原因,每当我创建或删除子表时我都必须重新编译该函数,以便该函数使用最新的插入规则。

编辑: 额外的皱纹
这种技术有一个小问题:如果由于另一个错误(例如,在我的情况下,CHECK 约束失败),此 EXECUTE/PERFORM 操作在第一次尝试时回滚,那么包含此代码的函数似乎缓存对使用 EXECUTE 创建的回滚的partition_insert()函数的引用,并且后续调用由于找不到缓存的对象而失败。

我通过在定义数据库时为每个所需的表类型参数预先创建函数的存根版本来解决此问题。