Como evitar várias funções evals com a sintaxe (func ()). * Em uma consulta SQL?
Quando uma função retorna umTABLE
ou umSETOF composite-type
, como esta função de amostra:
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;
os resultados podem ser acessados por vários métodos:
1)select * from func(3)
irá produzir estas colunas de saída:
i | j ---+--- 1 | 3 2 | 9 3 | 27
2)select func(3)
produzirá apenas uma coluna de saída do tipo ROW.
func ------- (1,3) (2,9) (3,27)
3)select (func(3)).*
irá produzir como # 1:
i | j ---+--- 1 | 3 2 | 9 3 | 27
Quando o argumento da função vem de uma tabela ou de uma subconsulta, a sintaxe # 3 é a única possível, como em:
select N, (func(N)).* from (select 2 as N union select 3 as N) s;
ou como neste relacionadoresponda. Se tivéssemosLATERAL JOIN
nós poderíamos usar isso, mas até que o PostgreSQL 9.3 esteja fora, não é suportado, e as versões anteriores ainda serão usadas por anos de qualquer maneira.
Agora o problema com a sintaxe # 3 é que a função é chamada quantas vezes houver colunas no resultado. Não há razão aparente para isso, mas acontece. Podemos ver isso na versão 9.2, adicionando umRAISE NOTICE 'called for %', n
na função. Com a consulta acima, ele gera:
NOTICE: called for 2 NOTICE: called for 2 NOTICE: called for 3 NOTICE: called for 3
Agora, se a função for alterada para retornar 4 colunas, assim:
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;
então a mesma saída de consulta:
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 chamadas de função foram necessárias, 8 foram realmente feitas. A proporção é o número de colunas de saída.
Com a sintaxe # 2 que produz o mesmo resultado, exceto pelo layout das colunas de saída, essas várias chamadas não acontecem:
select N,func(N) from (select 2 as N union select 3 as N) s;
dá:
NOTICE: called for 2 NOTICE: called for 3
seguido pelas 6 linhas resultantes:
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)Questões
Existe uma sintaxe ou uma construção com 9.2 que alcançaria o resultado esperado fazendo apenas as chamadas de função mínimas necessárias?
Pergunta bônus: por que as avaliações múltiplas acontecem?