我们假设我们有这个功能:
create or replace function foo(a integer)
returns table (b integer, c integer)
language plpgsql
as $$
begin
raise notice 'foo()';
return query select a*2, a*4;
return query select a*6, a*8;
return query select a*10, a*12;
end;
$$;
“加注通知'foo()'”部分将用于了解调用该函数的时间。
如果我以这种方式调用函数:
postgres=# SELECT i, foo(i) as bla FROM generate_series(1,3) as i;
NOTICE: foo()
NOTICE: foo()
NOTICE: foo()
i | bla
---+---------
1 | (2,4)
1 | (6,8)
1 | (10,12)
2 | (4,8)
2 | (12,16)
2 | (20,24)
3 | (6,12)
3 | (18,24)
3 | (30,36)
(9 rows)
我们可以看到,正如预期的那样,foo()被调用3次。
但是如果我以这种方式调用函数(所以我实际上得到foo()得到不同的列):
postgres=# SELECT i, (foo(i)).* FROM generate_series(1,3) as i;
NOTICE: foo()
NOTICE: foo()
NOTICE: foo()
NOTICE: foo()
NOTICE: foo()
NOTICE: foo()
i | b | c
---+----+----
1 | 2 | 4
1 | 6 | 8
1 | 10 | 12
2 | 4 | 8
2 | 12 | 16
2 | 20 | 24
3 | 6 | 12
3 | 18 | 24
3 | 30 | 36
(9 rows)
我们可以看到foo()被调用了6次。如果foo()返回3列,它将被调用9次。很明显,foo()会为它返回的每一列和每一列调用。
我不明白为什么postgres不会在这里进行优化。这对我来说是一个问题,因为我的(真正的)foo()可能是CPU密集型的。有什么想法吗?
编辑: 使用“不可变”函数或不返回多行的函数会产生相同的行为:
create or replace function foo(a integer)
returns table (b integer, c integer, d integer)
language plpgsql
immutable
as $$
begin
raise notice 'foo';
return query select a*2, a*3, a*4;
end;
$$;
postgres=# select i, (foo(i)).* from generate_series(1,2) as i;
NOTICE: foo
NOTICE: foo
NOTICE: foo
NOTICE: foo
NOTICE: foo
NOTICE: foo
i | b | c | d
---+---+---+---
1 | 2 | 3 | 4
2 | 4 | 6 | 8
(2 rows)
答案 0 :(得分:3)
基本上,在select
子句中调用返回多个值的函数(尤其是返回集合的函数)是合理的。
事实上,postgres没有对这样的调用进行任何优化。
将您的函数放在from
子句中。
SELECT i, f.* FROM generate_series(1,3) as i, foo(i) f;
在the documentation中,您可以找到该笔记(强调我的):
目前,还可以在select中调用返回集的函数 查询列表。对于查询自己生成的每一行, 调用函数返回集,并为其生成输出行 函数结果集的每个元素。但请注意这个 功能已弃用,可能会在将来的版本中删除。
答案 1 :(得分:3)
这是一个已知问题。
SELECT (f(x)).*
在分析时宏扩展到
SELECT (f(x)).a, (f(x)).b, ...
并且PostgreSQL不会将对同一函数的多次调用合并为一次调用。
为了避免这个问题,你可以将它包装在另一个子查询层中,以便宏扩展发生在对函数结果的简单引用而不是函数调用上:
select i, (f).*
FROM (
SELECT i, foo(i) f from generate_series(1,2) as i
) x(i, f)
或在FROM
子句中使用横向调用,这对于较新版本是首选:
select i, f.*
from generate_series(1,2) as i
CROSS JOIN LATERAL foo(i) f;
CROSS JOIN LATERAL
可以省略,使用遗留逗号连接和隐式横向连接,但我发现包含它非常清楚,特别是当你混合其他连接类型时。