15

我有许多使用 Postgres“分区”功能的表。我想在每个表上定义一个通用的 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

但是“新”记录在 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;

但这也不起作用,因为缓存了对临时表的引用:为什么在访问 PL/PgSQL 函数中的临时表时出现“与 OID ##### 的关系不存在”错误?

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

编辑:
正如@a​​lvherre 指出的那样,这可能可以在 Postgres 8.4 中使用 EXECUTE ... USING 语法来完成。请参阅http://wiki.postgresql.org/wiki/PL/pgSQL_Dynamic_Triggers中的示例

4

3 回答 3

22

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

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

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

于 2010-01-04T03:49:52.497 回答
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 || ').*';
于 2010-08-09T04:30:23.087 回答
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);

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

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

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

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

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

于 2010-01-04T05:52:47.120 回答