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.