Você pode envolvê-lo em uma subconsulta, mas isso não é garantido sem o
OFFSET 0
hackear. Na versão 9.3, use LATERAL
. O problema é causado pelo analisador que expande efetivamente a macro *
em uma lista de colunas. Solução
Onde:
SELECT (my_func(x)).* FROM some_table;
avaliará
my_func
n
vezes para n
colunas de resultados da função, esta formulação:SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table
) sub;
geralmente não, e tende a não adicionar uma varredura adicional em tempo de execução. Para garantir que a avaliação múltipla não seja realizada, você pode usar o
OFFSET 0
hackear ou abusar da falha do PostgreSQL em otimizar além dos limites do CTE:SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table OFFSET 0
) sub;
ou:
WITH tmp(mf) AS (
SELECT my_func(x) FROM some_table
)
SELECT (mf).* FROM tmp;
No PostgreSQL 9.3 você pode usar
LATERAL
para obter um comportamento mais saudável:SELECT mf.*
FROM some_table
LEFT JOIN LATERAL my_func(some_table.x) AS mf ON true;
LEFT JOIN LATERAL ... ON true
mantém todas as linhas como a consulta original, mesmo que a chamada da função não retorne nenhuma linha. Demonstração
Crie uma função que não seja inlineável como demonstração:
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;
e uma tabela de dados fictícios:
CREATE TABLE some_table AS SELECT x FROM generate_series(1,10) x;
então tente as versões acima. Você verá que o primeiro gera três avisos por invocação; o último só levanta um.
Por quê?
Boa pergunta. É horrível.
Parece:
(func(x)).*
é expandido como:
(my_func(x)).i, (func(x)).j, (func(x)).k, (func(x)).l
na análise, de acordo com uma olhada em
debug_print_parse
, debug_print_rewritten
e debug_print_plan
. A árvore de análise (aparada) se parece com isso: :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
...
}
)
Então, basicamente, estamos usando um hack parser burro para expandir curingas clonando nós.