扩展复合结果时,避免对同一函数进行多次调用

时间:2014-06-24 16:00:47

标签: sql postgresql query-optimization plpgsql

我有一个SQL函数重新调整复合结果。

CREATE TYPE result_t AS (a int, b int, c int, d numeric);

CREATE OR REPLACE FUNCTION slow_function(int)
RETURNS result_t
AS $$
    -- just some placeholder code to make it slow
    SELECT 0, 0, 0, (
        SELECT sum(ln(i::numeric))
        FROM generate_series(1, $1) i
    )
$$ LANGUAGE sql IMMUTABLE;

调用函数时,我希望将复合类型的部分扩展为多个列。我打电话时工作正常:

SELECT (slow_function(i)).*
FROM generate_series(0, 200) i

a     b     c     d                    
----  ----  ----  -------------------- 
0     0     0     (null)               
0     0     0     0                    
0     0     0     0.6931471805599453   
0     0     0     1.791759469228055
...
Total runtime: 6196.754 ms

不幸的是,这导致每个结果列一次调用一次,这不必要地慢。这可以通过将运行时间与查询进行比较来测试,该查询直接返回复合结果并运行速度快四倍:

SELECT slow_function(i)
FROM generate_series(0, 200) i
...
Total runtime: 1561.476 ms

示例代码也位于http://sqlfiddle.com/#!15/703ba/7

如何在不浪费CPU能力的情况下获得多列结果?

3 个答案:

答案 0 :(得分:2)

也许你想要的是一个LATERAL子查询。

SELECT t.id, f.* FROM some_table t, LATERAL (SELECT slow_func(t.id)) f

这将为每一行调用该函数一次,然后将结果“解包”到输出中的列中。任何子查询都将用于“展开”,但LATERAL允许您引用其他子句中的列。

我相信LATERAL是在PostgreSQL 9.3中引入的

答案 1 :(得分:1)

解决此问题的一种方法是使用WITH子句。

WITH t AS (
    SELECT slow_function(i) AS s
    FROM generate_series(0, 200) i
)
SELECT (s).*
FROM t

这是有效的,因为在执行其余查询之前,WITH子句的结果会被实现。但是对于更复杂的情况,这种解决方案通常是坏的,因为它大大减少了查询优化器的选项,以便以其他方式改进查询执行。所以我仍然在寻找更好的方法来解决这个问题。

答案 2 :(得分:1)

甚至不需要CTE。 普通子查询也可以完成这项工作(使用第9.3页测试):

SELECT i, (f).*                     -- decompose here
FROM  (
   SELECT i, (slow_func(i)) AS f    -- do not decompose here
   FROM   generate_series(1, 3) i
   ) sub;

请确保不分解子查询中函数的复合结果。为外部查询保留。
当然需要一个众所周知的类型。无法使用匿名记录。

或者,what @Richard wrote LATERAL JOIN 也有效。语法可以更简单:

SELECT * FROM generate_series(1, 3) i, slow_func(i) f
  • LATERAL在Postgres 9.3或更高版本中隐式应用。
  • 一个函数可以在FROM子句中独立存在,不必包含在附加的子选择中。想象一下它的位置。

SQL Fiddle所有变体的EXPLAIN VERBOSE输出。如果发生这种情况,你可以函数的多重评估。

COST设置

通常(对于此特定查询无关紧要),请确保对您的函数应用高成本设置,以便规划人员知道避免更频繁地进行评估。像:

CREATE OR REPLACE FUNCTION slow_function(int)
  RETURNS result_t AS
$func$
    -- expensive body
$func$ LANGUAGE sql IMMUTABLE COST 100000;

Per documentation:

  

较大的值会导致规划人员试图避免不必要地评估函数。