ฟังก์ชันเดียวกันใน SELECT และ WHERE clause


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?
Evan Carroll

คำตอบ:


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)

อย่างที่คุณเห็นฟังก์ชั่นนี้ถูกเรียกอย่างน้อยหนึ่งครั้ง (จากWHEREclause) และเมื่อเงื่อนไขเป็นจริงอีกครั้งเพื่อสร้างเอาต์พุต

เพื่อหลีกเลี่ยงการประมวลผลครั้งที่สองคุณสามารถทำสิ่งที่เอ็ดการ์แนะนำ - คือห่อคำค้นและกรองชุดผลลัพธ์:

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 ครั้งในข้อความค้นหาทั้งสอง


การตัดสินใจที่จะเรียกใช้ฟังก์ชั่นอีกครั้งจะถูกกำหนดโดยการมีคำสั่งด้านผลกระทบในร่างกายของฟังก์ชั่น? เป็นไปได้หรือไม่ที่จะทราบว่าฟังก์ชั่นที่มีพารามิเตอร์เดียวกันถูกเรียกหนึ่งครั้งหรือหลายครั้งต่อแถวโดยดูจากแบบสอบถามแผน (เช่นไม่มีส่วนที่มีผลข้างเคียง) หรือไม่
Andriy M

@AndriyM ฉันสามารถจินตนาการใช่ แต่ขณะนี้ไม่มีเวลาเล่นกับดีบักเกอร์เพื่อดูสิ่งที่เรียกว่าจริง จะเพิ่มเล็กน้อยเกี่ยวกับฟังก์ชั่นอินไลน์ (ซึ่งไม่ใช่กรณีที่ OP ควรคาดหวังตามที่ได้ยิน)
dezso

1
@AndriyM ตาม: postgresql.org/docs/9.1/static/sql-createfunction.htmlฟังก์ชันจะถือว่าเป็น VOLATILE หากไม่ได้ประกาศเป็น IMMUTABLE หรือเสถียร VOLATILE บ่งชี้ว่าค่าฟังก์ชั่นสามารถเปลี่ยนแปลงได้แม้ในการสแกนตารางเดียวดังนั้นจึงไม่มีการปรับให้เหมาะสม
Lennart

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.