Có nhiều cách khác nhau đơn giản hơn và nhanh hơn.
2x DISTINCT ON
SELECT *
FROM (
SELECT DISTINCT ON (name)
name, week AS first_week, value AS first_val
FROM tbl
ORDER BY name, week
) f
JOIN (
SELECT DISTINCT ON (name)
name, week AS last_week, value AS last_val
FROM tbl
ORDER BY name, week DESC
) l USING (name);
Hoặc ngắn hơn:
SELECT *
FROM (SELECT DISTINCT ON (1) name, week AS first_week, value AS first_val FROM tbl ORDER BY 1,2) f
JOIN (SELECT DISTINCT ON (1) name, week AS last_week , value AS last_val FROM tbl ORDER BY 1,2 DESC) l USING (name);
Đơn giản và dễ hiểu. Cũng nhanh nhất trong các bài kiểm tra cũ của tôi. Giải thích chi tiết cho DISTINCT ON
:
- Chọn hàng đầu tiên trong mỗi GROUP BY nhóm?
Chức năng cửa sổ 2x, 1x DISTINCT ON
SELECT DISTINCT ON (name)
name, week AS first_week, value AS first_val
, first_value(week) OVER w AS last_week
, first_value(value) OVER w AS last_value
FROM tbl t
WINDOW w AS (PARTITION BY name ORDER BY week DESC)
ORDER BY name, week;
WINDOW
rõ ràng mệnh đề chỉ rút ngắn mã, không ảnh hưởng đến hiệu suất.
first_value()
thuộc loại hỗn hợp
Các hàm tổng hợp min()
hoặc max()
không chấp nhận các loại kết hợp làm đầu vào. Bạn sẽ phải tạo các hàm tổng hợp tùy chỉnh (không khó lắm).
Nhưng các hàm cửa sổ first_value()
và last_value()
làm . Dựa trên đó, chúng tôi có thể đưa ra các giải pháp đơn giản:
Truy vấn đơn giản
SELECT DISTINCT ON (name)
name, week AS first_week, value AS first_value
,(first_value((week, value)) OVER (PARTITION BY name ORDER BY week DESC))::text AS l
FROM tbl t
ORDER BY name, week;
Đầu ra có tất cả dữ liệu, nhưng các giá trị của tuần trước được đưa vào một bản ghi ẩn danh (tùy chọn truyền sang text
). Bạn có thể cần các giá trị đã phân tách.
Kết quả bị phân rã với việc sử dụng loại bảng một cách có cơ hội
Để làm được điều đó, chúng tôi cần một loại composite nổi tiếng. Định nghĩa bảng được điều chỉnh sẽ cho phép sử dụng trực tiếp loại bảng một cách có cơ hội:
CREATE TABLE tbl (week int, value int, name text); -- optimized column order
week
và giá trị value
đến trước, vì vậy bây giờ chúng ta có thể sắp xếp theo chính loại bảng:
SELECT (l).name, first_week, first_val
, (l).week AS last_week, (l).value AS last_val
FROM (
SELECT DISTINCT ON (name)
week AS first_week, value AS first_val
, first_value(t) OVER (PARTITION BY name ORDER BY week DESC) AS l
FROM tbl t
ORDER BY name, week
) sub;
Kết quả được phân tách từ loại hàng do người dùng xác định
Điều đó có lẽ không thể xảy ra trong hầu hết các trường hợp. Đăng ký kiểu kết hợp với CREATE TYPE
(vĩnh viễn) hoặc với CREATE TEMP TABLE
(trong suốt thời gian của phiên):
CREATE TEMP TABLE nv(last_week int, last_val int); -- register composite type
SELECT name, first_week, first_val, (l).last_week, (l).last_val
FROM (
SELECT DISTINCT ON (name)
name, week AS first_week, value AS first_val
, first_value((week, value)::nv) OVER (PARTITION BY name ORDER BY week DESC) AS l
FROM tbl t
ORDER BY name, week
) sub;
Các hàm tổng hợp tùy chỉnh first()
&last()
Tạo các hàm và tổng hợp một lần cho mỗi cơ sở dữ liệu:
CREATE OR REPLACE FUNCTION public.first_agg (anyelement, anyelement)
RETURNS anyelement
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT $1;'
CREATE AGGREGATE public.first(anyelement) (
SFUNC = public.first_agg
, STYPE = anyelement
, PARALLEL = safe
);
CREATE OR REPLACE FUNCTION public.last_agg (anyelement, anyelement)
RETURNS anyelement
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE AS
'SELECT $2';
CREATE AGGREGATE public.last(anyelement) (
SFUNC = public.last_agg
, STYPE = anyelement
, PARALLEL = safe
);
Sau đó:
SELECT name
, first(week) AS first_week, first(value) AS first_val
, last(week) AS last_week , last(value) AS last_val
FROM (SELECT * FROM tbl ORDER BY name, week) t
GROUP BY name;
Có lẽ là giải pháp thanh lịch nhất. Nhanh hơn với mô-đun bổ sung first_last_agg
cung cấp triển khai C.
So sánh các hướng dẫn trong Postgres Wiki.
Có liên quan:
- Tính toán mức tăng trưởng người theo dõi theo thời gian cho mỗi người có ảnh hưởng
db <> fiddle here (hiển thị tất cả)
Old sqlfiddle
Mỗi truy vấn này về cơ bản nhanh hơn đáng kể so với câu trả lời hiện được chấp nhận trong một bài kiểm tra nhanh trên bảng có 50 nghìn hàng với EXPLAIN ANALYZE
.
Có nhiều cách hơn. Tùy thuộc vào phân phối dữ liệu, các kiểu truy vấn khác nhau có thể nhanh hơn (nhiều). Xem:
- Tối ưu hóa truy vấn GROUP BY để truy xuất hàng mới nhất cho mỗi người dùng