Misma función en la cláusula SELECT y WHERE


11

Pregunta de principiante:

Tengo una función costosa f(x, y)en dos columnas x e y en mi tabla de base de datos.

Quiero ejecutar una consulta que me da el resultado de la función como una columna y le impone una restricción, algo así como

SELECT *, f(x, y) AS func FROM table_name WHERE func < 10;

Sin embargo, esto no funciona, así que tendré que escribir algo como

SELECT *, f(x, y) AS func FROM table_name WHERE f(x, y) < 10;

¿Funcionará esto la costosa función dos veces? ¿Cuál es la mejor manera de hacer esto?


1
¿Es la función STABLE/ IMMUTABLEo VOLATILE?
Evan Carroll

Respuestas:


22

Creemos una función que tenga un efecto secundario para que podamos ver cuántas veces se ejecuta:

CREATE OR REPLACE FUNCTION test.this_here(val integer)
    RETURNS numeric
    LANGUAGE plpgsql
AS $function$
BEGIN
    RAISE WARNING 'I am called with %', val;
    RETURN sqrt(val);
END;
$function$;

Y luego llama a esto como lo haces:

SELECT this_here(i) FROM generate_series(1,10) AS t(i) WHERE this_here(i) < 2;

WARNING:  I am called with 1
WARNING:  I am called with 1
WARNING:  I am called with 2
WARNING:  I am called with 2
WARNING:  I am called with 3
WARNING:  I am called with 3
WARNING:  I am called with 4
WARNING:  I am called with 5
WARNING:  I am called with 6
WARNING:  I am called with 7
WARNING:  I am called with 8
WARNING:  I am called with 9
WARNING:  I am called with 10
    this_here     
──────────────────
                1
  1.4142135623731
 1.73205080756888
(3 rows)

Como puede ver, la función se llama al menos una vez (de la WHEREcláusula), y cuando la condición es verdadera, una vez más para producir la salida.

Para evitar la segunda ejecución, puede hacer lo que sugiere Edgar , es decir, ajustar la consulta y filtrar el conjunto de resultados:

SELECT * 
  FROM (SELECT this_here(i) AS val FROM generate_series(1,10) AS t(i)) x 
 WHERE x.val < 2;

WARNING:  I am called with 1
... every value only once ...
WARNING:  I am called with 10

Para verificar aún más cómo funciona esto, uno puede ir pg_stat_user_functionsy verificar callsallí (dado track_functionsse establece en 'todos).

Probemos con algo que no tenga efectos secundarios:

CREATE OR REPLACE FUNCTION test.simple(val numeric)
 RETURNS numeric
 LANGUAGE sql
AS $function$
SELECT sqrt(val);
$function$;

SELECT simple(i) AS v 
  FROM generate_series(1,10) AS t(i)
 WHERE simple(i) < 2;
-- output omitted

SELECT * FROM pg_stat_user_functions WHERE funcname = 'simple';
-- 0 rows

simple()en realidad es demasiado simple, por lo que puede estar en línea , por lo tanto, no aparece en la vista. Hagámoslo insalvable:

CREATE OR REPLACE FUNCTION test.other_one(val numeric)
 RETURNS numeric
 LANGUAGE sql
AS $function$
SELECT 1; -- to prevent inlining
SELECT sqrt(val);
$function$;

SELECT other_one(i) AS v
  FROM generate_series(1,10) AS t(i)
 WHERE other_one(i) < 2;

SELECT * FROM pg_stat_user_functions ;
 funcid  schemaname  funcname   calls  total_time  self_time 
────────┼────────────┼───────────┼───────┼────────────┼───────────
 124311  test        other_one     13       0.218      0.218

SELECT *
  FROM (SELECT other_one(i) AS v FROM generate_series(1,10) AS t(i)) x 
 WHERE v < 2;

SELECT * FROM pg_stat_user_functions ;
 funcid  schemaname  funcname   calls  total_time  self_time 
────────┼────────────┼───────────┼───────┼────────────┼───────────
 124311  test        other_one     23       0.293      0.293

Como se ve, la imagen es la misma con o sin efectos secundarios.

Cambiar other_one()a IMMUTABLEcambia el comportamiento (quizás sorprendentemente) a peor, ya que se llamará 13 veces en ambas consultas.


¿Podría la decisión de volver a llamar a la función estar determinada por la presencia de una instrucción de efectos secundarios en el cuerpo de la función? ¿Es posible averiguar si una función con los mismos parámetros se llama una o varias veces por fila mirando el plan de consulta (si, por ejemplo, no tenía una parte de efectos secundarios)?
Andriy M

@AndriyM Me imagino que sí, pero actualmente no tengo tiempo para jugar con un depurador para ver cómo se llama realmente. Agregará un poco sobre las funciones en línea (que no es el caso que el OP debe esperar, como parece).
dezso el

1
@AndriyM, de acuerdo con: postgresql.org/docs/9.1/static/sql-createfunction.html , se supone que una función es VOLÁTIL si no se declara como INMUTABLE o ESTABLE. VOLATILE indica que el valor de la función puede cambiar incluso dentro de un escaneo de una sola tabla, por lo que no se pueden realizar optimizaciones.
Lennart el

5

Intenta llamarlo de nuevo:

SELECT
     *
FROM (
SELECT
     *,
     f(x, y) AS func
FROM table_name
) a
WHERE a.func < 10;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.