Та же функция в предложении SELECT и WHERE

11

Вопрос новичка:

У меня есть дорогая функция f(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 раз в обоих запросах.

Dezso
источник
Может ли решение о повторном вызове функции быть определено присутствием инструкции побочного действия в теле функции? Можно ли выяснить, вызывается ли функция с одним и тем же параметром (ами) один или несколько раз в строке, просматривая план запроса (если, например, он не имеет побочных эффектов)?
Андрей М
@AndriyM Я могу себе представить, что да, но в настоящее время нет времени поиграть с отладчиком, чтобы увидеть, что на самом деле называется. Добавим немного о встроенных функциях (что не соответствует ожиданиям OP, как это звучит).
Дезсо
1
@AndriyM, в соответствии с: postgresql.org/docs/9.1/static/sql-createfunction.html функция считается VOLATILE, если она не объявлена ​​как IMMUTABLE или STABLE. VOLATILE указывает, что значение функции может измениться даже в пределах одного сканирования таблицы, поэтому никакие оптимизации не могут быть выполнены.
Леннарт
5

Попробуйте позвонить еще раз:

SELECT
     *
FROM (
SELECT
     *,
     f(x, y) AS func
FROM table_name
) a
WHERE a.func < 10;
Эдгар Аллан Байрон
источник