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

Nux*_*Nux 3 postgresql dynamic-sql plpgsql 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');
Run Code Online (Sandbox Code Playgroud)

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

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

旁注:所有模式创建函数 ( ALTER TABLE, CREATE TABLE...) 工作正常。似乎只有数据操作函数会受到影响 ( SELECT, INSERT, UPDATE)。

解决方法

前:

IF (
    SELECT max(id) FROM dimtime
)
THEN
    INSERT INTO dimtime SELECT * FROM public.src_dimtime;
END IF;
Run Code Online (Sandbox Code Playgroud)

后:

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

编辑:该问题出现在 PostgreSQL 9.2 中,但似乎没有出现在 9.3 中。也许它是固定的?

Erw*_*ter 5

这种行为是可以预料的。原因是 PL/pgSQL对 SQL 语句使用计划缓存,内部使用标准的预准备语句。

根据文档:

由于每个表达式和 SQL 命令首先在函数中执行,PL/pgSQL 解释器创建一个准备好的执行计划(使用 SPI 管理器SPI_prepare和SPI_saveplan函数)。对该表达式或命令的后续访问重复使用准备好的计划。

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

准备好的语句在session的生命周期内保存,而不仅仅是事务(但在底层对象更改时失效,这对于并发访问是安全的)。文档再次:

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

大胆强调我的。

如果你想“改变”一个表名的模式,你真的会引用一个完全不同的表,并且需要使用动态 SQL with EXECUTE,它每次都会生成一个新计划(具有所有优点和缺点):

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

阅读手册中的参考章节。它相当全面。

代码示例

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

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