sql >> Databasteknik >  >> RDS >> PostgreSQL

Hur undviker man flera funktionsevaler med syntaxen (func()).* i en SQL-fråga?

Du kan slå in det i en underfråga men det är inte garanterat säkert utan OFFSET 0 hacka. I 9.3, använd LATERAL . Problemet orsakas av att parsern effektivt makroexpanderar * i en kolumnlista.

Lösning

Var:

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

kommer att utvärdera my_func n gånger för n resultatkolumner från funktionen, denna formulering:

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

kommer i allmänhet inte att göra det och tenderar inte att lägga till en extra skanning vid körning. För att garantera att flera utvärderingar inte kommer att utföras kan du använda OFFSET 0 hacka eller missbruka PostgreSQL:s misslyckande med att optimera över CTE-gränser:

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

eller:

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

I PostgreSQL 9.3 kan du använda LATERAL för att få ett sundare beteende:

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

LEFT JOIN LATERAL ... ON true behåller alla rader som den ursprungliga frågan, även om funktionsanropet inte returnerar någon rad.

Demo

Skapa en funktion som inte är inlinebar som en demonstration:

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;

och en tabell med dummydata:

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

prova sedan ovanstående versioner. Du kommer att se att den första väcker tre meddelanden per anrop; de senare höjer bara en.

Varför?

Bra fråga. Det är hemskt.

Det ser ut som:

(func(x)).*

utökas som:

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

i parsning, enligt en titt på debug_print_parse , debug_print_rewritten och debug_print_plan . Det (trimmade) analysträdet ser ut så här:

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

Så i grund och botten använder vi ett dumt parserhack för att utöka jokertecken genom att klona noder.




  1. Databaskryptering:De tre typerna och varför du behöver dem

  2. Hämta databas eller någon annan fil från den interna lagringen med hjälp av run-as

  3. Hur botar jag orsaken till viloläge undantag IllegalArgumentException inträffade när setter anropades?

  4. PostgreSQL returnerar resultatet satt som JSON-array?