我没有看到此实现中的错误:
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!
答案 0 :(得分:4)
您的问题是由于SQL语句的计划方式。 SQL对数据类型非常严格。 Postgres函数为多态伪类型ANYELEMENT
提供了一些灵活性,但SQL语句仍然是静态地使用给定类型进行规划。
如果$1::int>2
不是$1
,那么表达式integer
永远不会执行(你可以避免以这种方式除以零),这无法拯救你来自规划查询的早期阶段出现的 语法 错误。
您仍然可以使用您拥有的功能 。使用无类型字符串文字:
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 boolean
而不是FROM
) {1}}列出:SELECT * FROM foo()
而不是SELECT foo()
。对于可以内联的简单IMMUTABLE
函数,看起来查询计划的处理方式不同。
除此之外,请使用:
pg_typeof($1) = 'integer'::regtype
而不是:
<击> (pg_typeof($1)::text)='integer'
击>
这通常更好。每次使用一次而不是计算值时,总是更好。这适用于类型名称的已知别名。
答案 1 :(得分:3)
它与SQL计划程序/优化程序完全相关。由于函数声明为IMMUTABLE
,优化器会尝试预先评估查询部分。出于某种原因,即使您使用$1::int>2
参数调用函数,它也会评估表达式text
。
如果您将foo
功能更改为VOLATILE
,它将正常工作,因为查询优化器不会尝试优化/预先评估它。
但是为什么bar
函数即使它是IMMUTABLE
也能正常工作?我想优化器决定不预先评估它,因为它不在循环中使用表达式。我的意思是$1::int>2
仅被评估一次,而在foo
函数中,它被评估多次。
似乎SQL计划程序对SQL
和PLPGSQL
语言的工作方式存在一些差异。 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