Hàm tương tự trong mệnh đề SELECT và WHERE


11

Câu hỏi dành cho người mới bắt đầu:

Tôi có một hàm đắt tiền f(x, y)trên hai cột x và y trong bảng cơ sở dữ liệu của mình.

Tôi muốn thực hiện một truy vấn cung cấp cho tôi kết quả của hàm dưới dạng một cột và đặt một ràng buộc cho nó, đại loại như

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

Tuy nhiên điều này không hiệu quả, vì vậy tôi sẽ phải viết một cái gì đó như

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

Điều này sẽ chạy chức năng đắt tiền hai lần? Cách tốt nhất để làm điều này là gì?


1
Là chức năng STABLE/ IMMUTABLEhay VOLATILE?
Evan Carroll

Câu trả lời:


22

Hãy tạo một hàm có tác dụng phụ để chúng ta có thể thấy nó được thực thi bao nhiêu lần:

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

Và sau đó gọi nó như bạn làm:

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)

Như bạn thấy, hàm được gọi ít nhất một lần (từ WHEREmệnh đề) và khi điều kiện là đúng, một lần nữa để tạo đầu ra.

Để tránh thực hiện lần thứ hai, bạn có thể thực hiện những gì Edgar gợi ý - cụ thể là bọc truy vấn và lọc tập kết quả:

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

Để kiểm tra thêm cách thức hoạt động của nó, người ta có thể đi đến pg_stat_user_functionsvà kiểm tra callsở đó (được cung cấptrack_functions đặt thành 'tất cả).

Hãy thử với thứ gì đó không có tác dụng phụ:

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()thực sự quá đơn giản nên có thể được nội tuyến , do đó nó không xuất hiện trong chế độ xem. Hãy làm cho nó không thể inlin:

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

Như vẻ ngoài của nó, hình ảnh là giống nhau có hoặc không có tác dụng phụ.

Thay đổi other_one()để IMMUTABLEthay đổi hành vi (có thể đáng ngạc nhiên) trở nên tồi tệ hơn, vì nó sẽ được gọi 13 lần trong cả hai truy vấn.


Quyết định gọi lại chức năng có thể được xác định bởi sự hiện diện của một hướng dẫn tác dụng phụ trong cơ thể của chức năng không? Có thể tìm hiểu xem một hàm có cùng tham số được gọi một lần hay nhiều lần trên mỗi hàng bằng cách xem kế hoạch truy vấn (ví dụ: nếu nó không có phần tác dụng phụ)?
Andriy M

@AndriyM Tôi có thể tưởng tượng là có, nhưng hiện tại không có thời gian để chơi với trình gỡ lỗi để xem những gì thực sự được gọi là. Sẽ thêm một chút về các chức năng được nội tuyến (đó không phải là trường hợp mà OP nên mong đợi, vì nó có vẻ như).
dezso

1
@AndriyM, theo: postgresql.org/docs/9.1/static/sql-createfeft.html một chức năng được coi là TÌNH TRẠNG nếu không được khai báo là IMMUTABLE hoặc STABLE. VOLATILE chỉ ra rằng giá trị hàm có thể thay đổi ngay cả trong một lần quét bảng, do đó không thể tối ưu hóa.
Lennart

5

Hãy thử gọi lại:

SELECT
     *
FROM (
SELECT
     *,
     f(x, y) AS func
FROM table_name
) a
WHERE a.func < 10;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.