4

我没有看到这个实现中的错误:

CREATE FUNCTION foo(anyelement) RETURNS SETOF int  AS $f$
    SELECT id FROM unnest(array[1,2,3]) t(id) 
    WHERE CASE WHEN (pg_typeof($1)::text)='integer' THEN $1::int>2 ELSE true END
$f$ LANGUAGE SQL IMMUTABLE;

SELECT * FROM foo(123); -- OK!
SELECT * FROM foo('test'::text); -- BUG

这是某种 PostgreSQL 错误还是对anyelement数据类型的未记录限制?


有趣的是:当被隔离时,CASE子句可以正常工作:

 CREATE FUNCTION bar(anyelement) RETURNS boolean  AS $f$
   SELECT CASE WHEN (pg_typeof($1)::text)='integer' THEN $1::int>2;
 $f$ LANGUAGE SQL IMMUTABLE;

 SELECT bar('test'::text), bar(123), bar(1); -- works fine! 
4

2 回答 2

3

您的问题是由于 SQL 语句的计划方式造成的。SQL 对数据类型非常严格。Postgres 函数为多态伪类型提供了一些灵活性ANYELEMENT,但 SQL 语句仍然是使用给定类型静态规划的。

虽然if is not an$1::int>2永远不会执行表达式(您可以通过这种方式避免除以零),但这不能让您避免在规划查询的早期阶段出现的语法错误。$1integer

您仍然可以使用您拥有的功能做一些事情。使用无类型的字符串文字:

CREATE OR REPLACE FUNCTION foo(anyelement)
  RETURNS SETOF int AS
 $func$
   SELECT id FROM unnest(array[1,2,3]) id
   WHERE  CASE WHEN pg_typeof($1) = 'integer'::regtype
               THEN $1 > '2'  -- use a string literal!
               ELSE true END
$func$ LANGUAGE sql IMMUTABLE;

这至少适用于所有字符和数字数据类型。字符串文字被强制转换为提供的数据类型。但是对于“2”无效的其他数据类型,它仍然会失败。

值得注意的,您的第二个示例不会触发语法错误。从我对 Postgres 9.5 的测试中可以看出,如果函数不是,或者对于在列表中调用的IMMUTABLE集合返回函数(RETURNS SETOF ...而不是),则会触发语法错误:而不是。对于可以内联的简单函数,查询计划的处理方式似乎有所不同。RETURNS booleanFROMSELECT * FROM foo()SELECT foo()IMMUTABLE


另外,使用:

pg_typeof($1) = 'integer'::regtype

代替:

(pg_typeof($1)::text)='integer'

这通常更好。每次转换一次常量而不是计算值总是更好。这也适用于类型名称的已知别名。

于 2016-03-16T03:37:56.500 回答
2

它肯定与 SQL 规划器/优化器有关。由于函数被声明为IMMUTABLE,优化器尝试预先评估查询部分。出于某种原因,$1::int>2即使您使用text参数调用函数,它也会计算表达式。

如果您将foo函数更改为VOLATILE它会正常工作,因为查询优化器不会尝试优化/预评估它。

但是为什么bar即使它是函数也能正常工作IMMUTABLE?我猜优化器决定不预先评估它,因为它不在循环中使用表达式。我的意思是$1::int>2它只被评估一次,而在foo函数中它被评估多次。


似乎 SQL 规划器的工作方式SQLPLPGSQL语言存在一些差异。相同的功能PLPGSQL可以正常工作。

CREATE FUNCTION foo2(anyelement) RETURNS SETOF int AS $f$
DECLARE 
    i INTEGER;
BEGIN
    FOR i IN SELECT id FROM unnest(array[1,2,3]) t(id) 
        WHERE 
            CASE WHEN pg_typeof($1) = 'integer'::regtype 
                THEN $1::int > 2
                ELSE true END
    LOOP
        RETURN NEXT i;
    END LOOP;
END;
$f$ LANGUAGE plpgsql IMMUTABLE;

SELECT * FROM foo2('test'::text); -- works fine
于 2016-03-16T04:55:18.130 回答