强制PostgreSQL在函数中使用不同的模式

时间:2014-09-04 14:20:25

标签: postgresql plpgsql dynamic-sql search-path

我想为PG SQL数据库中的模式创建更新功能。测试功能如下。它不起作用,因为它永远不会发出通知,但在运行test_schema_update('second')时会这样做。

CREATE OR REPLACE FUNCTION test_schema_update(my_schema_name VARCHAR(200)) 
RETURNS void AS
$__marker__$
DECLARE
    actualValue varchar(1000);
    testValue varchar(1000);
BEGIN
    EXECUTE 'SET search_path TO ' || quote_ident(my_schema_name);

    testValue := (SELECT max(value) FROM setting WHERE settingkey = 'libraryname');
    EXECUTE ('SELECT max(value) FROM setting WHERE settingkey = ''libraryname''')
        INTO actualValue;

    IF (actualValue != testValue)
    THEN
        RAISE NOTICE '% != %', actualValue, testValue;
        RAISE INFO 'Schema was: %', current_schema();
    END IF;

    RESET search_path;
END;
$__marker__$ LANGUAGE plpgsql;

test_schema_update('first');
test_schema_update('second');

问题是PG SQL似乎每个会话只分析一次SELECT语句,然后将表固定到特定的模式。有趣的是,你会得到Schema was: second

那么有没有办法重置SELECT语句分析或其他一些方法来解决这个问题?

附注:所有架构创建功能(ALTER TABLECREATE TABLE ...)都可以正常工作。只有数据操作功能似乎受到影响(SELECTINSERTUPDATE)。

解决方法

在:

IF (
    SELECT max(id) FROM dimtime
)
THEN
    INSERT INTO dimtime SELECT * FROM public.src_dimtime;
END IF;

后:

EXECUTE ('
    SELECT max(id) FROM dimtime
')
INTO testInt;
IF (testInt IS NULL)
THEN
    EXECUTE 'INSERT INTO dimtime SELECT * FROM public.src_dimtime';
END IF;

编辑:问题出现在PostgreSQL 9.2中,但似乎不会出现在9.3中。也许这是固定的?

1 个答案:

答案 0 :(得分:4)

这种行为是可以预期的。原因是PL / pgSQL在SQL语句中使用计划缓存,内部使用标准的预处理语句

Per documentation:

  

首先在函数中执行每个表达式和SQL命令,   PL / pgSQL解释器创建一个准备好的执行计划(使用   SPI经理的SPI_prepareSPI_saveplan功能)。随后   访问该表达式或命令重用已准备好的计划。

这也是为什么plpgsql函数通常比复杂操作的纯SQL函数更快的原因:

预备语句将在会话的生命周期内保存,而不仅仅是事务(但在基础对象更改时无效,这对于并发访问是安全的)。 The documentation once again:

  

PL / pgSQL为a中的特定命令制定了执行计划   函数,它将重用该计划用于数据库的生命周期   连接即可。这通常是性能的胜利,但它可能会导致   如果动态更改数据库架构,则会出现一些问题。

大胆强调我的。

如果你想"改变"表名的模式,你真的要引用一个完全不同的表, 需要 使用带有EXECUTE的动态SQL,这会产生一个新的计划每一次(各有利弊):

  

因为PL / pgSQL以这种方式保存执行计划,所以SQL命令   直接出现在PL / pgSQL函数中必须引用相同的表   和每次执行的列;也就是说,你不能使用参数   SQL命令中的表或列的名称。绕过这个   限制,您可以使用PL / pgSQL构造动态命令   EXECUTE声明 - 以构建新执行计划为代价   在每次执行时。

阅读手册中引用的章节。它非常全面。

代码示例

您不需要动态SQL来添加代码示例,单个语句会更快:

INSERT INTO dimtime  -- you may want list columns
SELECT *             -- here as well
FROM   public.src_dimtime
WHERE  NOT EXISTS (SELECT 1 FROM dimtime);