SELECTとWHERE句で同じ関数


11

初心者向けの質問:

f(x, y)データベーステーブルの2つの列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;

これは高価な関数を2回実行しますか?これを行う最良の方法は何ですか?


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)

ご覧のとおり、関数は少なくとも1回(WHERE節から)呼び出され、条件がtrueの場合はもう一度呼び出されて出力が生成されます。

2回目の実行を回避するには、エドガーが提案することを実行できます。つまり、クエリをラップして結果セットをフィルター処理します。

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_functionscallsそこに移動して確認できます(ただしtrack_functions、「all」に設定されています)。

副作用のないもので試してみましょう:

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回呼び出されるため、動作が(おそらく驚くべきことに)悪化します。


関数を再度呼び出すかどうかの決定は、関数の本体に副作用のある命令が存在することによって判断できますか?クエリプランを調べることで、同じパラメーターを持つ関数が行ごとに1回呼び出されるか、複数回呼び出されるかを確認できますか(たとえば、副作用部分がない場合)。
Andriy M

@AndriyMはい、想像できますが、現在、実際に何が呼び出されているかを確認するためにデバッガで遊ぶ時間はありません。インライン化された関数について少し追加します(これは、OPが想定しているとおりではありません)。
dezso 2018年

1
@AndriyMによると、postgresql.org / docs / 9.1 / static / sql-createfunction.html IMMUTABLEまたはSTABLEとして宣言されていない場合、関数はVOLATILEであると見なされます。VOLATILEは、関数の値が単一のテーブルスキャン内でも変更される可能性があるため、最適化を実行できないことを示します。
Lennart 2018年

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.