select postgresql 中的 function() 对 function() 的调用过多

select function() in postgresql makes too much calls to function()

假设我们有这个函数:

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;
$$;

"raise notice '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() returning 3 列,它将被调用 9 次。很明显,foo() 会为每个 i 和它的每一列调用 returns.

我不明白postgres为什么不在这里做优化。这对我来说是个问题,因为我的(真实的)foo() 可能 CPU 密集。有什么想法吗?

编辑: 使用 "immutable" 函数或不 return 多行的函数给出相同的行为:

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)

基本上,在select 子句中不调用return 多个值的函数(尤其是函数returning 集合)是合理的。 事实上 postgres 并没有对这样的调用做任何优化。 将您的函数放在 from 子句中。

SELECT i, f.* FROM generate_series(1,3) as i, foo(i) f;

the documentation 你可以找到注释(强调我的):

Currently, functions returning sets can also be called in the select list of a query. For each row that the query generates by itself, the function returning set is invoked, and an output row is generated for each element of the function's result set. Note, however, that this capability is deprecated and might be removed in future releases.

这是一个已知问题。

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,使用遗留逗号连接和隐式横向连接,但我发现包含它相当清楚,尤其是当您混合使用其他连接类型时。