PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como evitar várias avaliações de função com a sintaxe (func()).* em uma consulta SQL?


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.