上下文
当一个函数返回一个TABLE
或一个SETOF 复合类型
时,就像这个:
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;
可以通过多种方法访问结果:
select * from func(3)
将产生这些输出列:
i | j
---+---
1 | 3
2 | 9
3 | 27
select func(3)
将只产生一个 ROW 类型的输出列。
func
-------
(1,3)
(2,9)
(3,27)
select (func(3)).*
将产生类似 #1 的结果:
i | j
---+---
1 | 3
2 | 9
3 | 27
当函数参数来自表或子查询时,语法 #3 是唯一可能的语法,如:
select N, (func(N)).*
from (select 2 as N union select 3 as N) s;
或与此相关的answer .如果我们有 LATERAL JOIN
,我们可以使用它,但在 PostgreSQL 9.3 推出之前,它不受支持,而且以前的版本仍将使用多年。
问题
语法 #3 的问题在于函数被调用的次数与结果中的列数一样多。没有明显的原因,但它确实发生了。
我们可以在 9.2 版本中通过在函数中添加一个 RAISE NOTICE 'called for %', n
来看到它。通过上面的查询,它输出:
NOTICE: called for 2
NOTICE: called for 2
NOTICE: called for 3
NOTICE: called for 3
现在,如果函数更改为返回 4 列,如下所示:
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;
然后相同的查询输出:
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
需要 2 次函数调用,实际调用了 8 次。该比率是输出列的数量。
语法 #2 产生除了输出列布局之外相同的结果,这些多重调用不会发生:
select N, func(N)
from (select 2 as N union select 3 as N) s;
给出:
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 的语法或构造可以通过仅执行最少的必需函数调用来实现预期结果?
额外的问题:为什么会发生多重评估?
最佳答案
您可以将其包装在一个子查询中,但如果没有 OFFSET 0
hack,则不能保证安全。在 9.3 中,使用 LATERAL
。问题是由解析器有效地将 *
宏扩展到列列表中引起的。
解决方法
地点:
SELECT (my_func(x)).* FROM some_table;
将为函数的 n
结果列评估 my_func
n
次,此公式:
SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table
) sub;
一般不会,也不会在运行时添加额外的扫描。为保证不会执行多重评估,您可以使用 OFFSET 0
hack 或滥用 PostgreSQL 无法跨 CTE 边界进行优化:
SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table OFFSET 0
) sub;
或:
WITH tmp(mf) AS (
SELECT my_func(x) FROM some_table
)
SELECT (mf).* FROM tmp;
在 PostgreSQL 9.3 中,您可以使用 LATERAL
来获得更明智的行为:
SELECT mf.*
FROM some_table
LEFT JOIN LATERAL my_func(some_table.x) AS mf ON true;
LEFT JOIN LATERAL ... 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_print_parse
、debug_print_rewritten
和 debug_print_plan
查看。 (修剪后的)解析树如下所示:
: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
...
}
)
所以基本上,我们使用一个愚蠢的解析器 hack 通过克隆节点来扩展通配符。
关于sql - 如何在查询中使用 (func()).* 语法避免多个函数求值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18369778/