Hal pertama yang pertama:Anda bisa gunakan hasil dari CTE beberapa kali dalam kueri yang sama, itulah fitur utama dari CTE .) Apa yang Anda miliki akan bekerja seperti ini (sementara masih menggunakan CTE sekali saja):
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
Peringatan 1:rank()
rank()
dapat mengembalikan beberapa baris per person_id
dengan rank = 1
. DISTINCT ON (person_id)
(seperti yang disediakan Gordon) adalah pengganti yang berlaku untuk row_number()
- yang bekerja untuk Anda, sebagai info tambahan diklarifikasi. Lihat:
Peringatan 2:ORDER BY submission_date DESC
Baik submission_date
atau last_updated
didefinisikan NOT NULL
. Dapat menjadi masalah dengan ORDER BY submission_date DESC, last_updated DESC ...
Lihat:
Haruskah kolom itu benar-benar NOT NULL
?
Anda menjawab:
String kosong tidak diperbolehkan untuk tipe date
. Jaga agar kolom tetap nullable. NULL
adalah nilai yang tepat untuk kasus tersebut. Gunakan NULLS LAST
seperti yang ditunjukkan untuk menghindari NULL
sedang diurutkan di atas.
Peringatan 3:OFFSET
Jika OFFSET
sama atau lebih besar dari jumlah baris yang dikembalikan oleh CTE, Anda mendapatkan tidak ada baris , jadi juga tidak ada jumlah total. Lihat:
Solusi sementara
Mengatasi semua peringatan sejauh ini, dan berdasarkan informasi tambahan, kami mungkin sampai pada pertanyaan ini:
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;
Sekarang CTE sebenarnya digunakan dua kali. RIGHT JOIN
menjamin kami mendapatkan jumlah total, tidak peduli OFFSET
. DISTINCT ON
harus melakukan OK-ish untuk hanya beberapa baris per (person_id)
dalam kueri dasar.
Tapi Anda memiliki baris yang lebar. Berapa lebar rata-rata? Kueri kemungkinan akan menghasilkan pemindaian berurutan di seluruh tabel. Indeks tidak akan membantu (banyak). Semua ini akan tetap sangat tidak efisien untuk paging . Lihat:
Anda tidak dapat melibatkan indeks untuk paging karena indeks tersebut didasarkan pada tabel turunan dari CTE. Dan kriteria pengurutan Anda yang sebenarnya untuk paging masih belum jelas (ORDER BY id
?). Jika paging adalah tujuannya, Anda sangat membutuhkan gaya kueri yang berbeda. Jika Anda hanya tertarik pada beberapa halaman pertama, Anda memerlukan gaya kueri yang berbeda. Solusi terbaik tergantung pada informasi yang masih hilang dalam pertanyaan ...
Lebih cepat secara radikal
Untuk tujuan Anda yang diperbarui:
(Mengabaikan "untuk kriteria filter tertentu, jenis, rencana, status" untuk kesederhanaan.)
Dan:
Berdasarkan dua indeks khusus ini :
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);
Jalankan kueri ini:
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;
Setiap set tanda kurung di sini wajib diisi.
Tingkat kecanggihan ini harus mengambil satu set baris teratas yang relatif kecil secara radikal lebih cepat dengan menggunakan indeks yang diberikan dan tanpa pemindaian berurutan. Lihat:
submission_date
kemungkinan besar harus ketik timestamptz
atau date
, bukan - yang merupakan definisi tipe aneh di Postgres dalam hal apa pun. Lihat:character varying(255)
Lebih banyak detail mungkin dioptimalkan, tetapi ini tidak terkendali. Anda dapat mempertimbangkan konsultasi profesional.