【问题标题】:select function() in postgresql makes too much calls to function() [duplicate]postgresql中的select function()对function()的调用过多[重复]
【发布时间】:2015-09-09 22:15:25
【问题描述】:

假设我们有这个函数:

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() 返回 3 列,它会被调用 9 次。很明显,对于每个 i 及其返回的每一列都会调用 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)

【问题讨论】:

    标签: sql postgresql stored-procedures plpgsql


    【解决方案1】:

    这是一个已知问题。

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

    【讨论】:

    • 什么是“x(i, f)”?它只是一个花哨的别名还是其他什么?如果我用随机字符串替换它,请求仍然有效。
    • @user368507 是的,它指定了子查询返回的表和列。表名部分是必需的。列,如果省略,默认为内部查询的列名。
    【解决方案2】:

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

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

    the documentation 你可以找到注释(重点是我的):

    目前,返回集合的函数也可以在select中调用 查询列表。对于查询自己生成的每一行, 调用返回集合的函数,并为 函数结果集的每个元素。但是请注意,这 功能已弃用,可能会在未来的版本中删除。

    【讨论】:

    • select 子句中调用函数是完全合理的——但在select 子句中调用返回set(=多行)的函数是不合理的
    • 该函数不返回集合 - 它返回一行。在SELECTLATERAL 中调用时,集合返回函数有不同的问题。
    • @klin Er。是的。我恳求-ENOCOFFEE。多次调用的问题不是通过返回一个集合来调用,而是返回一个集合。
    猜你喜欢
    • 2017-06-08
    • 1970-01-01
    • 2016-09-29
    • 2011-04-01
    • 2012-02-13
    • 1970-01-01
    • 2011-06-12
    • 2012-03-17
    • 2014-04-12
    相关资源
    最近更新 更多