SELECT 和 WHERE 子句中的相同功能

Jac*_*ack 11 postgresql postgresql-9.4

初学者问题:

f(x, y)在数据库表中的两列 x 和 y 上有一个昂贵的函数。

我想执行一个查询,该查询将函数的结果作为列提供给我并对其施加约束,例如

SELECT *, f(x, y) AS func FROM table_name WHERE func < 10;
Run Code Online (Sandbox Code Playgroud)

但是这不起作用,所以我将不得不写一些类似的东西

SELECT *, f(x, y) AS func FROM table_name WHERE f(x, y) < 10;
Run Code Online (Sandbox Code Playgroud)

这会运行两次昂贵的函数吗?做到这一点的最佳方法是什么?

dez*_*zso 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$;
Run Code Online (Sandbox Code Playgroud)

然后像你一样调用它:

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)
Run Code Online (Sandbox Code Playgroud)

如您所见,该函数至少被调用一次(从WHERE子句中),当条件为真时,再次产生输出。

为了避免第二次执行,您可以按照Edgar 的建议进行操作- 即包装查询并过滤结果集:

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
Run Code Online (Sandbox Code Playgroud)

要进一步检查这是如何工作的,可以去那里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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

看起来,无论有没有副作用,图片都是一样的。

更改other_one()IMMUTABLE将行为(可能令人惊讶)变得更糟,因为它将在两个查询中被调用 13 次。


Edg*_*ron 5

尝试再次调用它:

SELECT
     *
FROM (
SELECT
     *,
     f(x, y) AS func
FROM table_name
) a
WHERE a.func < 10;
Run Code Online (Sandbox Code Playgroud)