sql >> Database >  >> RDS >> PostgreSQL

Hoe kan ik meerdere functie-evaluaties vermijden met de (func()).* syntaxis in een SQL-query?

Je kunt het in een subquery verpakken, maar dat is niet gegarandeerd veilig zonder de OFFSET 0 hacken. Gebruik in 9.3 LATERAL . Het probleem wordt veroorzaakt doordat de parser effectief macro-uitbreidt * in een kolomlijst.

Tussenoplossing

Waar:

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

zal my_func evaluate evalueren n tijden voor n resultaatkolommen van de functie, deze formulering:

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

over het algemeen niet, en heeft de neiging om geen extra scan toe te voegen tijdens runtime. Om te garanderen dat meerdere evaluaties niet worden uitgevoerd, kunt u de OFFSET 0 . gebruiken hack of misbruik het falen van PostgreSQL om over CTE-grenzen heen te optimaliseren:

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

of:

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

In PostgreSQL 9.3 kunt u LATERAL . gebruiken om gezonder gedrag te krijgen:

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

LEFT JOIN LATERAL ... ON true behoudt alle rijen zoals de originele query, zelfs als de functieaanroep geen rij retourneert.

Demo

Maak een functie die niet inlineable is als demonstratie:

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;

en een tabel met dummy-gegevens:

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

probeer dan de bovenstaande versies. U zult zien dat de eerste drie kennisgevingen per aanroep oproept; de laatste verhoogt er maar één.

Waarom?

Goede vraag. Het is verschrikkelijk.

Het ziet eruit als:

(func(x)).*

wordt uitgevouwen als:

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

bij het ontleden, volgens een blik op debug_print_parse , debug_print_rewritten en debug_print_plan . De (bijgesneden) ontledingsboom ziet er als volgt uit:

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

Dus eigenlijk gebruiken we een domme parser-hack om wildcards uit te breiden door knooppunten te klonen.




  1. MySQL - Hoe een willekeurig getal te genereren?

  2. Hoe weet ik of ik niet-toegewezen werk heb in een Oracle-transactie?

  3. Hoe Joda-Time te gebruiken met java.sql.Timestamp

  4. Aan de slag met Oracle Application Express-APEX