Ta sama funkcja w klauzulach SELECT i WHERE

11

Pytanie dla początkujących:

Mam drogą funkcję f(x, y)w dwóch kolumnach xiy w mojej tabeli bazy danych.

Chcę wykonać zapytanie, które daje mi wynik funkcji jako kolumny i nakłada na nią ograniczenie, coś w rodzaju

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

Jednak to nie działa, więc będę musiał napisać coś takiego

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

Czy uruchomi to kosztowną funkcję dwukrotnie? Jak najlepiej to zrobić?

Jack black
źródło
1
Czy funkcja STABLE/ IMMUTABLElub VOLATILE?
Evan Carroll

Odpowiedzi:

22

Utwórzmy funkcję, która ma efekt uboczny, abyśmy mogli zobaczyć, ile razy jest wykonywana:

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$;

A potem nazywaj to tak jak ty:

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)

Jak widać, funkcja jest wywoływana co najmniej raz (z WHEREklauzuli), a gdy warunek jest spełniony, ponownie w celu wygenerowania wyniku.

Aby uniknąć drugiego wykonania, możesz zrobić to, co sugeruje Edgar - mianowicie zawinąć zapytanie i odfiltrować zestaw wyników:

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

Aby dalej sprawdzić, jak to działa, można przejść do pg_stat_user_functionsi sprawdzić callstam (podana track_functionsjest ustawiona na „wszystkie”).

Spróbujmy z czymś, co nie ma skutków ubocznych:

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()jest w rzeczywistości zbyt prosty, więc można go wstawiać , dlatego nie pojawia się w widoku. Niech to będzie nieuniknione:

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

Wygląda to tak samo z efektami ubocznymi lub bez nich.

Zmiana other_one()do IMMUTABLEzmiany zachowań (może zaskakująco) do gorszego, jak to będzie nazywane 13 razy w obu zapytaniami.

dezso
źródło
Czy decyzja o ponownym wywołaniu funkcji może być zdeterminowana obecnością instrukcji pobudzającej w ciele funkcji? Czy można sprawdzić, czy funkcja o tym samym parametrze (parametrach) jest wywoływana raz czy wiele razy w rzędzie, patrząc na plan zapytań (jeśli na przykład nie ma części powodującej skutki uboczne)?
Andriy M
@AndriyM Mogę sobie wyobrazić tak, ale obecnie nie mam czasu na zabawę z debuggerem, aby zobaczyć, co tak naprawdę nazywa się. Dodam trochę o funkcjach wbudowanych (czego nie powinno się spodziewać OP, jak się wydaje).
dezso
1
@AndriyM, zgodnie z: postgresql.org/docs/9.1/static/sql-createfunction.html zakłada się, że funkcja jest lotna, jeśli nie zostanie zadeklarowana jako NIEZWYKŁA lub STABILNA. VOLATILE wskazuje, że wartość funkcji może się zmienić nawet w ramach skanowania pojedynczej tabeli, więc nie można dokonać optymalizacji.
Lennart
5

Spróbuj nazwać to jeszcze raz:

SELECT
     *
FROM (
SELECT
     *,
     f(x, y) AS func
FROM table_name
) a
WHERE a.func < 10;
Edgar Allan Bayron
źródło