SELECT ve WHERE yan tümcesinde aynı işlev


11

Başlangıç ​​sorusu:

f(x, y)Veritabanı tablomda iki sütun x ve y üzerinde pahalı bir işlevi var .

Bana bir sütun olarak işlevin sonucunu veren ve üzerinde bir kısıtlama koyan bir sorgu yürütmek istiyorum

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

Ancak bu işe yaramaz, bu yüzden böyle bir şey yazmak zorunda kalacağım

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

Bu pahalı işlevi iki kez çalıştırır mı? Bunu yapmanın en iyi yolu nedir?


1
STABLE/ IMMUTABLEVeya işlevi VOLATILEmi?
Evan Carroll

Yanıtlar:


22

Kaç kez yürütüldüğünü görebilmemiz için yan etkisi olan bir işlev oluşturalım:

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

Ve sonra bunu sizin gibi adlandırın:

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)

Gördüğünüz gibi, fonksiyon en az bir kez ( WHEREmaddeden) ve koşul doğru olduğunda, çıktıyı üretmek için bir kez daha çağrılır .

İkinci yürütmeyi önlemek için Edgar'ın önerisini yapabilirsiniz - sorguyu sarın ve sonuç kümesini filtreleyin:

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

Bunun nasıl çalıştığını daha fazla kontrol etmek için, oraya gidip pg_stat_user_functionskontrol edilebilir calls(verilen track_functions'hepsi' olarak ayarlanır).

Yan etkisi olmayan bir şeyle deneyelim:

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()aslında çok basittir, böylece satır içine alınabilir , bu nedenle görünümde görünmez. Bunu inline edilemez yapalım:

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

Göründüğü gibi, resim yan etkilerle veya yan etkiler olmadan aynıdır.

Değişen other_one()için IMMUTABLEher iki sorguları 13 defa adı verilecek şekilde, değişiklikleri daha kötü davranışı (belki şaşırtıcı).


İşlevi tekrar çağırma kararı, işlevin gövdesinde yan etkili bir yönerge bulunmasıyla belirlenebilir mi? Aynı parametreye / parametrelere sahip bir işlevin sorgu planına bakarak satır başına bir veya daha fazla kez çağrılıp çağrılmadığını bulmak mümkün mü (örneğin, yan etki yapan bir parçası yoksa)?
Andriy M

@AndriyM Evet hayal edebiliyorum, ama şu anda gerçekte ne denildiğini görmek için bir hata ayıklayıcı ile oynamak için zamanım yok. Satır içi işlevler hakkında biraz ekleyecektir (OP'nin göründüğü gibi beklemesi gereken durum bu değildir).
dezso

1
@AndriyM, şuna göre: postgresql.org/docs/9.1/static/sql-createfunction.html, bir fonksiyonun UYGUN DEĞİL veya STABİL olarak bildirilmediği takdirde VOLATİL olduğu varsayılır. VOLATILE, işlev değerinin tek bir tablo taramasında bile değişebileceğini, dolayısıyla hiçbir optimizasyon yapılamayacağını gösterir.
Lennart

5

Tekrar aramayı deneyin:

SELECT
     *
FROM (
SELECT
     *,
     f(x, y) AS func
FROM table_name
) a
WHERE a.func < 10;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.