Postgresql 如何避免在SQL查询中使用(func()).*语法进行多个函数求值? 上下文

Postgresql 如何避免在SQL查询中使用(func()).*语法进行多个函数求值? 上下文,postgresql,user-defined-functions,Postgresql,User Defined Functions,当函数返回一个表或一组复合类型时,如以下示例函数: 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; 可以通过各种方法访问结果: 1

当函数返回一个表或一组复合类型时,如以下示例函数:

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;
可以通过各种方法访问结果:

1从func3中选择*将生成以下输出列:

i | j ---+--- 1 | 3 2 | 9 3 | 27 3选择func3.*将产生如下结果:

i | j ---+--- 1 | 3 2 | 9 3 | 27 或者像这个相关的。如果我们有横向连接,我们可以使用它,但在PostgreSQL 9.3推出之前,它是不受支持的,而且以前的版本仍然会使用多年

问题 现在语法3的问题是,调用函数的次数与结果中的列数相同。没有明显的原因,但它确实发生了。 我们可以在9.2版中看到它,在函数中添加了一个提升通知“called for%”,n。通过上述查询,它将输出:

NOTICE: called for 2 NOTICE: called for 2 NOTICE: called for 3 NOTICE: called for 3 然后,相同的查询输出:

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 给出:

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中是否有语法或构造可以通过只执行所需的最小函数调用来实现预期结果


额外的问题:为什么会发生多次求值?

您可以将其封装在子查询中,但如果没有偏移量0攻击,则无法保证安全。在9.3中,使用横向。该问题是由解析器将*有效地扩展到列列表中引起的

变通办法 其中:

SELECT (my_func(x)).* FROM some_table;
将对函数中的n个结果列计算my_func n次,公式如下:

SELECT (mf).* FROM (
    SELECT my_func(x) AS mf FROM some_table
) sub;
通常不会,也不会在运行时添加额外的扫描。为了保证不会执行多次评估,您可以使用偏移量0攻击或滥用PostgreSQL未能跨CTE边界进行优化:

SELECT (mf).* FROM (
    SELECT my_func(x) AS mf FROM some_table OFFSET 0
) sub;
或:

在PostgreSQL 9.3中,您可以使用LATERAL来获得更理智的行为:

SELECT mf.*
FROM some_table
LEFT JOIN LATERAL my_func(some_table.x) AS mf ON true;
左连接侧。。。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;
和虚拟数据表:

CREATE TABLE some_table AS SELECT x FROM generate_series(1,10) x;
然后试试上面的版本。您将看到,第一个调用每次会引发三个通知;后者只提出一个问题

为什么? 好问题。太可怕了

它看起来像:

(func(x)).*
扩展为:

(my_func(x)).i, (func(x)).j, (func(x)).k, (func(x)).l
在解析中,根据debug\u print\u parse、debug\u print\u rewrited和debug\u print\u plan查看。修剪后的分析树如下所示:

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;
   :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 
       ...
      }
   )
因此,基本上,我们正在使用哑解析器黑客通过克隆节点来扩展通配符。

它必须在my_funcome_table.x中加入mf ON true,才能作为替代品。否则,如果函数调用没有返回任何行,我们可能会丢失行,这是因为横向默认为内部联接。
CREATE TABLE some_table AS SELECT x FROM generate_series(1,10) x;
(func(x)).*
(my_func(x)).i, (func(x)).j, (func(x)).k, (func(x)).l
   :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 
       ...
      }
   )