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.