Điều đầu tiên trước tiên:bạn có thể sử dụng kết quả từ một CTE nhiều lần trong cùng một truy vấn, đó là tính năng chính của của CTE .) Những gì bạn có sẽ hoạt động như thế này (trong khi vẫn sử dụng CTE một lần duy nhất):
WITH cte AS (
SELECT * FROM (
SELECT *, row_number() -- see below
OVER (PARTITION BY person_id
ORDER BY submission_date DESC NULLS LAST -- see below
, last_updated DESC NULLS LAST -- see below
, id DESC) AS rn
FROM tbl
) sub
WHERE rn = 1
AND status IN ('ACCEPTED', 'CORRECTED')
)
SELECT *, count(*) OVER () AS total_rows_in_cte
FROM cte
LIMIT 10
OFFSET 0; -- see below
Báo trước 1:rank()
rank()
có thể trả về nhiều hàng cho mỗi person_id
với rank = 1
. DISTINCT ON (person_id)
(như Gordon đã cung cấp) là sự thay thế có thể áp dụng cho row_number()
- cái nào phù hợp với bạn, vì thông tin bổ sung được làm rõ. Xem:
Báo trước 2:ORDER BY submission_date DESC
Cả submission_date
cũng không phải last_updated
được định nghĩa NOT NULL
. Có thể là sự cố với ORDER BY submission_date DESC, last_updated DESC ...
Xem:
Các cột đó có thực sự là NOT NULL
không ?
Bạn đã trả lời:
Chuỗi trống không được phép cho loại date
. Giữ cho các cột có giá trị rỗng. NULL
là giá trị thích hợp cho những trường hợp đó. Sử dụng NULLS LAST
như đã trình bày để tránh NULL
được sắp xếp trên cùng.
Báo trước 3:OFFSET
Nếu OFFSET
bằng hoặc lớn hơn số hàng được trả về bởi CTE, bạn nhận được không có hàng , vì vậy cũng không có tổng số. Xem:
Giải pháp tạm thời
Giải quyết tất cả các cảnh báo cho đến nay và dựa trên thông tin bổ sung, chúng tôi có thể đi đến truy vấn sau:
WITH cte AS (
SELECT DISTINCT ON (person_id) *
FROM tbl
WHERE status IN ('ACCEPTED', 'CORRECTED')
ORDER BY person_id, submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC
)
SELECT *
FROM (
TABLE cte
ORDER BY person_id -- ?? see below
LIMIT 10
OFFSET 0
) sub
RIGHT JOIN (SELECT count(*) FROM cte) c(total_rows_in_cte) ON true;
Bây giờ CTE thực sự là được sử dụng hai lần. RIGHT JOIN
đảm bảo rằng chúng tôi nhận được tổng số, bất kể OFFSET
. DISTINCT ON
nên thực hiện OK-ish cho một vài hàng duy nhất trên mỗi (person_id)
trong truy vấn cơ sở.
Nhưng bạn có hàng rộng. Chiều rộng trung bình là bao nhiêu? Truy vấn có thể sẽ dẫn đến việc quét tuần tự trên toàn bộ bảng. Chỉ mục sẽ không giúp được gì (nhiều). Tất cả những điều này sẽ vẫn cực kỳ kém hiệu quả cho việc phân trang . Xem:
Bạn không thể liên quan đến một chỉ mục để phân trang vì nó dựa trên bảng dẫn xuất từ CTE. Và tiêu chí sắp xếp thực tế của bạn cho phân trang vẫn chưa rõ ràng (ORDER BY id
?). Nếu phân trang là mục tiêu, bạn rất cần một kiểu truy vấn khác. Nếu bạn chỉ quan tâm đến một vài trang đầu tiên, bạn cần một kiểu truy vấn khác. Giải pháp tốt nhất phụ thuộc vào thông tin còn thiếu trong câu hỏi ...
Nhanh hơn hoàn toàn
Đối với mục tiêu cập nhật của bạn:
(Bỏ qua "cho các tiêu chí, loại, kế hoạch, trạng thái bộ lọc được chỉ định" vì sự đơn giản.)
Và:
Dựa trên hai chỉ số chuyên biệt này :
CREATE INDEX ON tbl (submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST)
WHERE status IN ('ACCEPTED', 'CORRECTED'); -- optional
CREATE INDEX ON tbl (person_id, submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST);
Chạy truy vấn này:
WITH RECURSIVE cte AS (
(
SELECT t -- whole row
FROM tbl t
WHERE status IN ('ACCEPTED', 'CORRECTED')
AND NOT EXISTS (SELECT FROM tbl
WHERE person_id = t.person_id
AND ( submission_date, last_updated, id)
> (t.submission_date, t.last_updated, t.id) -- row-wise comparison
)
ORDER BY submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST
LIMIT 1
)
UNION ALL
SELECT (SELECT t1 -- whole row
FROM tbl t1
WHERE ( t1.submission_date, t1.last_updated, t1.id)
< ((t).submission_date,(t).last_updated,(t).id) -- row-wise comparison
AND t1.status IN ('ACCEPTED', 'CORRECTED')
AND NOT EXISTS (SELECT FROM tbl
WHERE person_id = t1.person_id
AND ( submission_date, last_updated, id)
> (t1.submission_date, t1.last_updated, t1.id) -- row-wise comparison
)
ORDER BY submission_date DESC NULLS LAST, last_updated DESC NULLS LAST, id DESC NULLS LAST
LIMIT 1)
FROM cte c
WHERE (t).id IS NOT NULL
)
SELECT (t).*
FROM cte
LIMIT 10
OFFSET 0;
Mọi tập hợp các dấu ngoặc đơn ở đây là bắt buộc.
Mức độ phức tạp này sẽ truy xuất một tập hợp tương đối nhỏ các hàng trên cùng nhanh hơn hoàn toàn bằng cách sử dụng các chỉ số đã cho và không cần quét tuần tự. Xem:
submission_date
hầu hết có lẽ nên là loại timestamptz
hoặc date
, không - đó là một định nghĩa kiểu kỳ quặc trong Postgres trong mọi trường hợp. Xem:character varying(255)
Nhiều chi tiết khác có thể được tối ưu hóa, nhưng điều này đang vượt quá tầm tay. Bạn có thể cân nhắc việc tư vấn chuyên nghiệp.