Puede envolverlo en una subconsulta, pero no se garantiza que sea seguro sin el OFFSET 0
cortar a tajos. En 9.3, usa LATERAL
. El problema es causado por el analizador que expande macro efectivamente *
en una lista de columnas.
Solución alternativa
donde:
SELECT (my_func(x)).* FROM some_table;
evaluará my_func
n
tiempos para n
columnas de resultados de la función, esta formulación:
SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table
) sub;
generalmente no lo hará, y tiende a no agregar un escaneo adicional en tiempo de ejecución. Para garantizar que no se realizará una evaluación múltiple, puede usar el OFFSET 0
piratear o abusar de la falla de PostgreSQL para optimizar a través de los límites de CTE:
SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table OFFSET 0
) sub;
o:
WITH tmp(mf) AS (
SELECT my_func(x) FROM some_table
)
SELECT (mf).* FROM tmp;
En PostgreSQL 9.3 puedes usar LATERAL
para tener un comportamiento más sano:
SELECT mf.*
FROM some_table
LEFT JOIN LATERAL my_func(some_table.x) AS mf ON true;
LEFT JOIN LATERAL ... ON true
conserva todas las filas como la consulta original, incluso si la llamada a la función no devuelve ninguna fila.
Demostración
Cree una función que no sea en línea como demostración:
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;
y una tabla de datos ficticios:
CREATE TABLE some_table AS SELECT x FROM generate_series(1,10) x;
luego pruebe las versiones anteriores. Verás que el primero levanta tres avisos por invocación; estos últimos solo levantan uno.
¿Por qué?
Buena pregunta. Es horrible.
Parece:
(func(x)).*
se expande como:
(my_func(x)).i, (func(x)).j, (func(x)).k, (func(x)).l
en el análisis, según un vistazo a debug_print_parse
, debug_print_rewritten
y debug_print_plan
. El árbol de análisis (recortado) se ve así:
: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
...
}
)
Básicamente, estamos usando un truco tonto del analizador para expandir los comodines mediante la clonación de nodos.