Můžete to zabalit do dílčího dotazu, ale to není zaručeno bezpečné bez OFFSET 0
zaseknout. Ve verzi 9.3 použijte LATERAL
. Problém je způsoben tím, že analyzátor efektivně makro rozbalí *
do seznamu sloupců.
Řešení
Kde:
SELECT (my_func(x)).* FROM some_table;
vyhodnotí my_func
n
časy pro n
výsledné sloupce z funkce, tato formulace:
SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table
) sub;
obecně nebude a má tendenci nepřidávat další skenování za běhu. Chcete-li zaručit, že nebude provedeno vícenásobné vyhodnocení, můžete použít OFFSET 0
hacknout nebo zneužít selhání PostgreSQL při optimalizaci přes hranice CTE:
SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table OFFSET 0
) sub;
nebo:
WITH tmp(mf) AS (
SELECT my_func(x) FROM some_table
)
SELECT (mf).* FROM tmp;
V PostgreSQL 9.3 můžete použít LATERAL
abyste se chovali rozumněji:
SELECT mf.*
FROM some_table
LEFT JOIN LATERAL my_func(some_table.x) AS mf ON true;
LEFT JOIN LATERAL ... ON true
zachová všechny řádky jako původní dotaz, i když volání funkce nevrátí žádný řádek.
Ukázka
Vytvořte funkci, kterou nelze vložit jako ukázku:
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;
a tabulka fiktivních dat:
CREATE TABLE some_table AS SELECT x FROM generate_series(1,10) x;
pak vyzkoušejte výše uvedené verze. Uvidíte, že první vyvolá tři upozornění na vyvolání; posledně jmenovaný zvýší pouze jeden.
Proč?
Dobrá otázka. Je to hrozné.
Vypadá to takto:
(func(x)).*
je rozšířen jako:
(my_func(x)).i, (func(x)).j, (func(x)).k, (func(x)).l
při analýze, podle pohledu na debug_print_parse
, debug_print_rewritten
a debug_print_plan
. (oříznutý) strom analýzy vypadá takto:
: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
...
}
)
V zásadě tedy používáme hloupý analyzátor k rozšíření zástupných znaků klonováním uzlů.