Dan*_*ité 14 postgresql user-defined-functions
当函数返回a TABLE或a时SETOF composite-type,如此示例函数:
CREATE FUNCTION func(n int) returns table(i int, j bigint) as $$
BEGIN
RETURN QUERY select 1,n::bigint
union all select 2,n*n::bigint
union all select 3,n*n*n::bigint;
END
$$ language plpgsql;
Run Code Online (Sandbox Code Playgroud)
结果可以通过各种方法访问:
1)select * from func(3)将产生这些输出列:
i | j ---+--- 1 | 3 2 | 9 3 | 27
2)select func(3)将只生成一个ROW类型的输出列.
func ------- (1,3) (2,9) (3,27)
3)select (func(3)).*会产生#1:
i | j ---+--- 1 | 3 2 | 9 3 | 27
当函数参数来自表或子查询时,语法#3是唯一可能的语法,如:
select N, (func(N)).* from (select 2 as N union select 3 as N) s;
Run Code Online (Sandbox Code Playgroud)
或者在这个相关的答案中.如果我们LATERAL JOIN可以使用它,但是在PostgreSQL 9.3出来之前,它不受支持,并且以前的版本仍将使用多年.
现在语法#3的问题是函数被调用的次数与结果中的列一样多.没有明显的理由,但它发生了.我们可以在9.2版中通过RAISE NOTICE 'called for %', n在函数中添加一个来看到它.通过上面的查询,它输出:
NOTICE: called for 2 NOTICE: called for 2 NOTICE: called for 3 NOTICE: called for 3
现在,如果函数被更改为返回4列,如下所示:
CREATE FUNCTION func(n int) returns table(i int, j bigint,k int, l int) as $$
BEGIN
raise notice 'called for %', n;
RETURN QUERY select 1,n::bigint,1,1
union all select 2,n*n::bigint,1,1
union all select 3,n*n*n::bigint,1,1;
END
$$ language plpgsql stable;
Run Code Online (Sandbox Code Playgroud)
然后相同的查询输出:
NOTICE: called for 2 NOTICE: called for 2 NOTICE: called for 2 NOTICE: called for 2 NOTICE: called for 3 NOTICE: called for 3 NOTICE: called for 3 NOTICE: called for 3
需要2个函数调用,实际调用8个.比率是输出列的数量.
使用语法#2产生除输出列布局之外的相同结果时,不会发生这些多次调用:
select N,func(N) from (select 2 as N union select 3 as N) s;
Run Code Online (Sandbox Code Playgroud)
得到:
NOTICE: called for 2 NOTICE: called for 3
然后是6个结果行:
n | func ---+------------ 2 | (1,2,1,1) 2 | (2,4,1,1) 2 | (3,8,1,1) 3 | (1,3,1,1) 3 | (2,9,1,1) 3 | (3,27,1,1)
是否有一个语法或9.2的结构,通过只做最小的所需函数调用来实现预期的结果?
奖金问题:为什么多重评估会发生?
Cra*_*ger 18
您可以将其包装在子查询中,但如果没有OFFSET 0黑客攻击,则无法保证安全.在9.3中,使用LATERAL.问题是由解析器有效地宏扩展*到列列表引起的.
哪里:
SELECT (my_func(x)).* FROM some_table;
Run Code Online (Sandbox Code Playgroud)
将评估函数的结果列的my_func n时间n,这个公式:
SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table
) sub;
Run Code Online (Sandbox Code Playgroud)
通常不会,并且往往不会在运行时添加额外的扫描.为了保证不会执行多次评估,您可以使用OFFSET 0hack或滥用PostgreSQL无法跨CTE边界进行优化:
SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table OFFSET 0
) sub;
Run Code Online (Sandbox Code Playgroud)
要么:
WITH tmp(mf) AS (
SELECT my_func(x) FROM some_table
)
SELECT (mf).* FROM tmp;
Run Code Online (Sandbox Code Playgroud)
在PostgreSQL 9.3中,您可以使用LATERAL以获得更健全的行为:
SELECT mf.*
FROM some_table
LEFT JOIN LATERAL my_func(some_table.x) AS mf ON true;
Run Code Online (Sandbox Code Playgroud)
LEFT JOIN LATERAL ... ON true 保留所有行,如原始查询,即使函数调用不返回任何行.
创建一个不能作为演示内联的函数:
CREATE OR REPLACE FUNCTION my_func(integer)
RETURNS TABLE(a integer, b integer, c integer) AS $$
BEGIN
RAISE NOTICE 'my_func(%)',$1;
RETURN QUERY SELECT $1, $1, $1;
END;
$$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
和一个虚拟数据表:
CREATE TABLE some_table AS SELECT x FROM generate_series(1,10) x;
Run Code Online (Sandbox Code Playgroud)
然后尝试上面的版本.你会看到第一个每次调用都会引发三个通知; 后者只提出一个.
好问题.这太糟糕了.
看起来像:
(func(x)).*
Run Code Online (Sandbox Code Playgroud)
扩展为:
(my_func(x)).i, (func(x)).j, (func(x)).k, (func(x)).l
Run Code Online (Sandbox Code Playgroud)
解析,根据一看debug_print_parse,debug_print_rewritten和debug_print_plan.(修剪过的)解析树看起来像这样:
:targetList (
{TARGETENTRY
:expr
{FIELDSELECT
:arg
{FUNCEXPR
:funcid 57168
...
}
:fieldnum 1
:resulttype 23
:resulttypmod -1
:resultcollid 0
}
:resno 1
:resname i
...
}
{TARGETENTRY
:expr
{FIELDSELECT
:arg
{FUNCEXPR
:funcid 57168
...
}
:fieldnum 2
:resulttype 20
:resulttypmod -1
:resultcollid 0
}
:resno 2
:resname j
...
}
{TARGETENTRY
:expr
{FIELDSELECT
:arg
{FUNCEXPR
:funcid 57168
...
}
:fieldnum 3
:...
}
:resno 3
:resname k
...
}
{TARGETENTRY
:expr
{FIELDSELECT
:arg
{FUNCEXPR
:funcid 57168
...
}
:fieldnum 4
...
}
:resno 4
:resname l
...
}
)
Run Code Online (Sandbox Code Playgroud)
所以基本上,我们使用一个愚蠢的解析器黑客通过克隆节点来扩展通配符.