postgresql中的select function()调用函数()

时间:2015-06-23 21:55:10

标签: sql postgresql stored-procedures plpgsql

我们假设我们有这个功能:

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)

2 个答案:

答案 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可以省略,使用遗留逗号连接和隐式横向连接,但我发现包含它非常清楚,特别是当你混合其他连接类型时。