PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Come evitare più valori di funzione con la sintassi (func()).* in una query SQL?

Puoi racchiuderlo in una sottoquery ma non è garantito sicuro senza OFFSET 0 hackerare. In 9.3, usa LATERAL . Il problema è causato dal parser che espande efficacemente la macro di * in un elenco di colonne.

Soluzione alternativa

Dove:

SELECT (my_func(x)).* FROM some_table;

valuterà my_func n volte per n colonne dei risultati della funzione, questa formulazione:

SELECT (mf).* FROM (
    SELECT my_func(x) AS mf FROM some_table
) sub;

generalmente non lo farà e tende a non aggiungere una scansione aggiuntiva in fase di esecuzione. Per garantire che non vengano eseguite valutazioni multiple è possibile utilizzare il OFFSET 0 hackerare o abusare dell'incapacità di PostgreSQL di ottimizzare oltre i limiti CTE:

SELECT (mf).* FROM (
    SELECT my_func(x) AS mf FROM some_table OFFSET 0
) sub;

oppure:

WITH tmp(mf) AS (
    SELECT my_func(x) FROM some_table
)
SELECT (mf).* FROM tmp;

In PostgreSQL 9.3 puoi usare LATERAL per ottenere un comportamento più sano:

SELECT mf.*
FROM some_table
LEFT JOIN LATERAL my_func(some_table.x) AS mf ON true;

LEFT JOIN LATERAL ... ON true mantiene tutte le righe come la query originale, anche se la chiamata di funzione non restituisce alcuna riga.

Dimostrazione

Crea una funzione che non sia inlineabile come dimostrazione:

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 una tabella di dati fittizi:

CREATE TABLE some_table AS SELECT x FROM generate_series(1,10) x;

quindi prova le versioni precedenti. Vedrai che il primo genera tre avvisi per invocazione; questi ultimi ne alzano solo uno.

Perché?

Buona domanda. È orribile.

Sembra:

(func(x)).*

è ampliato come:

(my_func(x)).i, (func(x)).j, (func(x)).k, (func(x)).l

nell'analisi, secondo uno sguardo a debug_print_parse , debug_print_rewritten e debug_print_plan . L'albero di analisi (tagliato) ha il seguente aspetto:

   :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 
       ...
      }
   )

Quindi, in pratica, stiamo usando un hack del parser stupido per espandere i caratteri jolly clonando i nodi.