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

Hvordan undgår man flere funktionsevaler med (func()).*-syntaksen i en SQL-forespørgsel?

Du kan pakke det ind i en underforespørgsel, men det er ikke garanteret sikkert uden OFFSET 0 hacke. I 9.3 skal du bruge LATERAL . Problemet skyldes, at parseren effektivt makroudvider * ind i en kolonneliste.

Løsning

Hvor:

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

vil evaluere my_func n gange for n resultatkolonner fra funktionen, denne formulering:

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

vil generelt ikke, og har en tendens til ikke at tilføje en ekstra scanning under kørsel. For at garantere, at flere evalueringer ikke udføres, kan du bruge OFFSET 0 hack eller misbrug PostgreSQL's manglende optimering på tværs af 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 bruge LATERAL for at få en sundere adfærd:

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

LEFT JOIN LATERAL ... ON true bevarer alle rækker ligesom den oprindelige forespørgsel, selvom funktionskaldet ikke returnerer nogen række.

Demo

Opret en funktion, der ikke er 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;
 

og en tabel med dummy-data:

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

prøv derefter ovenstående versioner. Du vil se, at den første rejser tre meddelelser pr. påkaldelse; sidstnævnte rejser kun én.

Hvorfor?

Godt spørgsmål. Det er forfærdeligt.

Det ser ud som:

(func(x)).*
 

er udvidet som:

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

i parsing, ifølge et kig på debug_print_parse , debug_print_rewritten og debug_print_plan . Det (trimmede) parsetræ ser således ud:

: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å grundlæggende bruger vi et dumt parserhack til at udvide jokertegn ved at klone noder.




  1. Brug MySQL rumlige udvidelser til at vælge punkter inde i cirklen

  2. Hvordan rettes indholdsudbyderens url, der ikke findes i Android-indholdsudbyderen?

  3. MariaDB LOCALTIME() Forklaret

  4. Hvordan overfører man List fra Java til Oracle-procedure?