PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment éviter plusieurs évaluations de fonction avec la syntaxe (func()).* dans une requête SQL ?

Vous pouvez l'envelopper dans une sous-requête, mais ce n'est pas garanti sans le OFFSET 0 pirater. En 9.3, utilisez LATERAL . Le problème est dû au fait que l'analyseur macro-expanse effectivement * dans une liste de colonnes.

Solution

Où :

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

évaluera my_func n fois pour n colonnes de résultats de la fonction, cette formulation :

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

ne le fera généralement pas et n'ajoutera généralement pas d'analyse supplémentaire au moment de l'exécution. Pour garantir que plusieurs évaluations ne seront pas effectuées, vous pouvez utiliser le OFFSET 0 pirater ou abuser de l'échec de PostgreSQL à optimiser au-delà des limites 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;

Dans PostgreSQL 9.3, vous pouvez utiliser LATERAL pour avoir un comportement plus sain :

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

LEFT JOIN LATERAL ... ON true conserve toutes les lignes comme la requête d'origine, même si l'appel de fonction ne renvoie aucune ligne.

Démo

Créez une fonction qui n'est pas inlineable comme démonstration :

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;

et un tableau de données factices :

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

puis essayez les versions ci-dessus. Vous verrez que le premier lève trois avis par invocation; ces derniers n'en lèvent qu'un.

Pourquoi ?

Bonne question. C'est horrible.

Il ressemble à :

(func(x)).*

est développé comme :

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

dans l'analyse, selon un regard sur debug_print_parse , debug_print_rewritten et debug_print_plan . L'arbre d'analyse (coupé) ressemble à ceci :

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

Donc, fondamentalement, nous utilisons un hack d'analyseur stupide pour étendre les caractères génériques en clonant les nœuds.