Bạn có thể gói nó trong một truy vấn con nhưng điều đó không được đảm bảo an toàn nếu không có OFFSET 0
gian lận. Trong 9.3, sử dụng LATERAL
. Sự cố là do trình phân tích cú pháp mở rộng hiệu quả macro *
vào danh sách cột.
Giải pháp thay thế
Ở đâu:
SELECT (my_func(x)).* FROM some_table;
sẽ đánh giá my_func
n
thời gian cho n
cột kết quả từ hàm, công thức này:
SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table
) sub;
nói chung sẽ không và có xu hướng không thêm một lần quét bổ sung trong thời gian chạy. Để đảm bảo rằng nhiều lần đánh giá sẽ không được thực hiện, bạn có thể sử dụng OFFSET 0
hack hoặc lạm dụng PostgreSQL không thể tối ưu hóa qua các ranh giới CTE:
SELECT (mf).* FROM (
SELECT my_func(x) AS mf FROM some_table OFFSET 0
) sub;
hoặc:
WITH tmp(mf) AS (
SELECT my_func(x) FROM some_table
)
SELECT (mf).* FROM tmp;
Trong PostgreSQL 9.3, bạn có thể sử dụng LATERAL
để có được hành vi tốt hơn:
SELECT mf.*
FROM some_table
LEFT JOIN LATERAL my_func(some_table.x) AS mf ON true;
LEFT JOIN LATERAL ... ON true
giữ lại tất cả các hàng giống như truy vấn ban đầu, ngay cả khi lệnh gọi hàm không trả về hàng nào.
Bản trình diễn
Tạo một hàm không thể nội dòng như một trình diễn:
CREATE OR REPLACE FUNCTION my_func(integer)
RETURNS TABLE(a integer, b integer, c integer) AS $$
BEGIN
RAISE NOTICE 'my_func(%)',$1;
RETURN QUERY SELECT $1, $1, $1;
END;
$$ LANGUAGE plpgsql;
và một bảng dữ liệu giả:
CREATE TABLE some_table AS SELECT x FROM generate_series(1,10) x;
sau đó thử các phiên bản trên. Bạn sẽ thấy rằng điều đầu tiên nêu ra ba thông báo cho mỗi lần gọi; cái sau chỉ nâng một cái.
Tại sao?
Câu hỏi hay. Thật kinh khủng.
Nó trông giống như:
(func(x)).*
được mở rộng thành:
(my_func(x)).i, (func(x)).j, (func(x)).k, (func(x)).l
trong phân tích cú pháp, theo một cái nhìn tại debug_print_parse
, debug_print_rewritten
và debug_print_plan
. Cây phân tích cú pháp (đã được cắt tỉa) trông như thế này:
:targetList (
{TARGETENTRY
:expr
{FIELDSELECT
:arg
{FUNCEXPR
:funcid 57168
...
}
:fieldnum 1
:resulttype 23
:resulttypmod -1
:resultcollid 0
}
:resno 1
:resname i
...
}
{TARGETENTRY
:expr
{FIELDSELECT
:arg
{FUNCEXPR
:funcid 57168
...
}
:fieldnum 2
:resulttype 20
:resulttypmod -1
:resultcollid 0
}
:resno 2
:resname j
...
}
{TARGETENTRY
:expr
{FIELDSELECT
:arg
{FUNCEXPR
:funcid 57168
...
}
:fieldnum 3
:...
}
:resno 3
:resname k
...
}
{TARGETENTRY
:expr
{FIELDSELECT
:arg
{FUNCEXPR
:funcid 57168
...
}
:fieldnum 4
...
}
:resno 4
:resname l
...
}
)
Vì vậy, về cơ bản, chúng tôi đang sử dụng một bản hack phân tích cú pháp ngu ngốc để mở rộng các ký tự đại diện bằng cách sao chép các nút.