Контекст
Когда функция возвращает TABLE
или SETOF composite-type
, как эта функция-образец:
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) select * from func(3)
создаст эти выходные столбцы:
i | j ---+--- 1 | 3 2 | 9 3 | 27
2) select func(3)
будет выдавать только один выходной столбец типа ROW.
func ------- (1,3) (2,9) (3,27)
3) 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;
или как в этом связанном ответе. Если бы мы имели 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, который достиг бы ожидаемого результата, выполняя только минимально требуемые вызовы функций?
Бонусный вопрос: почему выполняются множественные оценки?