Однакова функція в пункті SELECT і WHERE


11

Питання для початківців:

Я маю дорогу функцію f(x, y)у двох стовпцях x і y в таблиці моєї бази даних.

Я хочу виконати запит, який дає мені результат функції у вигляді стовпця і накладає на нього обмеження, щось подібне

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

Однак це не працює, тому мені доведеться написати щось подібне

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

Чи буде це виконувати дорогу функцію двічі? Який найкращий спосіб зробити це?


1
Функція STABLE/ IMMUTABLEчи VOLATILE?
Еван Керролл

Відповіді:


22

Давайте створимо функцію, яка має побічний ефект, щоб ми могли побачити, скільки разів вона виконується:

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

А потім називайте це так, як ви:

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)

Як бачите, функція викликається принаймні один раз (з WHEREпункту), а коли умова справжня, ще раз для отримання результату.

Щоб уникнути другого виконання, ви можете зробити те, що пропонує Едгар, а саме - обернути запит і відфільтрувати набір результатів:

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

Для подальшої перевірки того, як це працює, можна перейти pg_stat_user_functionsі перевірити callsтам (дано track_functionsвстановлено значення "всі").

Давайте спробуємо з тим, що не має побічного ефекту:

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()насправді занадто простий, щоб його можна було накреслити , тому він не відображається у перегляді. Давайте зробимо це нездійсненним:

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

Як виглядає, картина однакова з побічними ефектами або без них.

Зміна, other_one()щоб IMMUTABLEзмінити поведінку (можливо, на диво) на гірше, оскільки вона буде називатися 13 разів в обох запитах.


Чи може рішення про повторний виклик функції визначатись наявністю побічної інструкції в тілі функції? Чи можна дізнатися, чи викликається функція з тими самими параметрами один раз чи кілька разів у рядку, переглянувши план запитів (якщо, наприклад, у нього не було побічної частини)?
Андрій М

@AndriyM Я можу уявити, що так, але в даний час немає часу грати з налагоджувачем, щоб побачити, що насправді викликається. Додамо трохи про вбудовані функції (це не так, як слід очікувати ОП, як це звучить).
dezso

1
@AndriyM, відповідно до: postgresql.org/docs/9.1/static/sql-createfunction.html функція передбачається ВОЛОТИЛЬНА, якщо не оголошена як НЕМАТИЧНА або СТАБІЛЬНА. VOLATILE вказує на те, що значення функції може змінюватися навіть у межах одного сканування таблиці, тому оптимізації не можна проводити.
Леннарт

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.