sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Cómo evitar múltiples evaluaciones de funciones con la sintaxis (func()).* en una consulta SQL?

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.