PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Wie vermeidet man mehrere Funktionsauswertungen mit der Syntax (func()).* in einer SQL-Abfrage?

Sie können es in eine Unterabfrage packen, aber das ist ohne den OFFSET 0 nicht garantiert sicher hacken. Verwenden Sie in 9.3 LATERAL . Das Problem wird dadurch verursacht, dass der Parser * effektiv makroexpandiert in eine Spaltenliste.

Problemumgehung

Wo:

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

wertet my_func aus n mal für n Ergebnisspalten der Funktion, diese Formulierung:

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

im Allgemeinen nicht und fügt in der Regel keinen zusätzlichen Scan zur Laufzeit hinzu. Um sicherzustellen, dass keine Mehrfachauswertung durchgeführt wird, können Sie den OFFSET 0 verwenden das Versagen von PostgreSQL bei der Optimierung über CTE-Grenzen hinweg zu hacken oder zu missbrauchen:

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

oder:

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

In PostgreSQL 9.3 können Sie LATERAL verwenden um ein vernünftigeres Verhalten zu erreichen:

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

LEFT JOIN LATERAL ... ON true behält alle Zeilen wie die ursprüngliche Abfrage bei, auch wenn der Funktionsaufruf keine Zeile zurückgibt.

Demo

Erstellen Sie zu Demonstrationszwecken eine Funktion, die nicht inlinefähig ist:

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;

und eine Tabelle mit Dummy-Daten:

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

dann versuchen Sie die oben genannten Versionen. Sie werden sehen, dass der erste drei Benachrichtigungen pro Aufruf auslöst; Letztere erhöhen nur einen.

Warum?

Gute Frage. Es ist schrecklich.

Es sieht so aus:

(func(x)).*

wird erweitert als:

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

beim Parsen, nach einem Blick auf debug_print_parse , debug_print_rewritten und debug_print_plan . Der (getrimmte) Parse-Baum sieht so aus:

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

Im Grunde verwenden wir also einen dummen Parser-Hack, um Wildcards durch Klonen von Knoten zu erweitern.