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

时间:2010-01-04 03:12:31

标签: 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

但是“NEW”记录在EXECUTE SQL中是不可见的。我怎样才能尽可能简单地完成这项工作?

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

我想过使用临时表:

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;

但由于对临时表的缓存引用,这也不起作用:Why do I get "relation with OID ##### does not exist" errors when accessing temporary tables in PL/PgSQL functions?

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

修改
正如@alvherre所指出的,这可以在Postgres 8.4中使用EXECUTE ... USING语法完成。请参阅http://wiki.postgresql.org/wiki/PL/pgSQL_Dynamic_Triggers

上的示例

3 个答案:

答案 0 :(得分:22)

您可以使用EXECUTE USING向其传递新内容。你的例子是

EXECUTE 'INSERT INTO ' || TG_RELID || '::regclass SELECT $1' USING NEW;

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

答案 1 :(得分:3)

是的,您可以在8.4中使用EXECUTE ... USING。例如:

EXECUTE 'INSERT INTO ' || table_name || ' SELECT $1.*' USING NEW;

在较低版本中(我仅在8.3中测试过),您可以使用:

EXECUTE 'INSERT INTO ' || table_name ||
    ' SELECT (' || quote_literal(NEW) || '::' || TG_RELID::regclass || ').*';

答案 2 :(得分:1)

我设法通过动态编译接受NEW行作为参数的函数来使其工作:

    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);

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

尽管是一个丑陋的kludge,这似乎做了这个工作。

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

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

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