我有一个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能力的情况下获得多列结果?
答案 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;
较大的值会导致规划人员试图避免不必要地评估函数。