DISTINCT ON
biasanya paling sederhana dan tercepat untuk ini di PostgreSQL .
(Untuk pengoptimalan kinerja untuk beban kerja tertentu, lihat di bawah.)
SELECT DISTINCT ON (customer)
id, customer, total
FROM purchases
ORDER BY customer, total DESC, id;
Atau lebih pendek (jika tidak sejelas) dengan nomor urut kolom keluaran:
SELECT DISTINCT ON (2)
id, customer, total
FROM purchases
ORDER BY 2, 3 DESC, 1;
Jika total
bisa NULL (tidak ada salahnya, tetapi Anda ingin mencocokkan indeks yang ada):
...
ORDER BY customer, total DESC NULLS LAST, id;
Poin utama
DISTINCT ON
adalah ekstensi PostgreSQL dari standar (di mana hanya DISTINCT
secara keseluruhan SELECT
daftar ditentukan).
Buat daftar sejumlah ekspresi dalam DISTINCT ON
klausa, nilai baris gabungan mendefinisikan duplikat. Panduan:
Jelas, dua baris dianggap berbeda jika mereka berbeda dalam setidaknya satu nilai kolom. Nilai nol dianggap sama dalam perbandingan ini.
Penekanan saya yang berani.
DISTINCT ON
dapat digabungkan dengan ORDER BY
. Ekspresi terdepan dalam ORDER BY
harus dalam kumpulan ekspresi di DISTINCT ON
, tetapi Anda dapat mengatur ulang urutan di antara mereka dengan bebas. Contoh.
Anda dapat menambahkan tambahan ekspresi ke ORDER BY
untuk memilih baris tertentu dari setiap kelompok rekan-rekan. Atau, seperti yang dikatakan manual:
DISTINCT ON
ekspresi harus cocok denganORDER BY
paling kiri ekspresi.ORDER BY
klausa biasanya akan berisi ekspresi tambahan yang menentukan prioritas baris yang diinginkan dalam setiapDISTINCT ON
grup.
Saya menambahkan id
sebagai item terakhir untuk memutuskan ikatan:
"Pilih baris dengan id
terkecil dari setiap grup berbagi total
tertinggi ."
Untuk mengurutkan hasil dengan cara yang tidak sesuai dengan urutan pengurutan yang menentukan yang pertama per grup, Anda dapat menyarangkan kueri di atas dalam kueri luar dengan ORDER BY
lain . Contoh.
Jika total
bisa NULL, Anda kemungkinan besar ingin baris dengan nilai bukan nol terbesar. Tambahkan NULLS LAST
seperti yang didemonstrasikan. Lihat:
- Urutkan menurut kolom ASC, tetapi nilai NULL terlebih dahulu?
SELECT
daftar tidak dibatasi oleh ekspresi dalam DISTINCT ON
atau ORDER BY
dengan cara apapun. (Tidak diperlukan dalam kasus sederhana di atas):
-
Anda tidak perlu sertakan ekspresi apa pun di
DISTINCT ON
atauORDER BY
. -
Anda bisa sertakan ekspresi lain di
SELECT
daftar. Ini penting untuk mengganti kueri yang jauh lebih kompleks dengan subkueri dan fungsi agregat/jendela.
Saya menguji dengan Postgres versi 8.3 – 13. Tetapi fitur tersebut telah ada setidaknya sejak versi 7.1, jadi pada dasarnya selalu.
Indeks
sempurna indeks untuk kueri di atas akan menjadi indeks multi-kolom yang mencakup ketiga kolom dalam urutan yang cocok dan dengan urutan pengurutan yang cocok:
CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);
Mungkin terlalu terspesialisasi. Tetapi gunakan jika kinerja baca untuk kueri tertentu sangat penting. Jika Anda memiliki DESC NULLS LAST
dalam kueri, gunakan yang sama dalam indeks sehingga urutan pengurutan cocok dan indeks dapat diterapkan.
Efektivitas / Pengoptimalan kinerja
Timbang biaya dan manfaat sebelum membuat indeks yang disesuaikan untuk setiap kueri. Potensi indeks di atas sangat bergantung pada distribusi data .
Indeks digunakan karena memberikan data yang telah diurutkan sebelumnya. Di Postgres 9.2 atau lebih baru, kueri juga dapat memanfaatkan pemindaian indeks saja jika indeks lebih kecil dari tabel yang mendasarinya. Namun, indeks harus dipindai secara keseluruhan.
Untuk sedikit baris per pelanggan (kardinalitas tinggi di kolom customer
), ini sangat efisien. Terlebih lagi jika Anda tetap membutuhkan output yang diurutkan. Manfaatnya berkurang dengan bertambahnya jumlah baris per pelanggan.
Idealnya, Anda memiliki cukup work_mem
untuk memproses langkah pengurutan yang terlibat dalam RAM dan tidak tumpah ke disk. Tapi secara umum setting work_mem
juga tinggi dapat memiliki efek samping. Pertimbangkan SET LOCAL
untuk pertanyaan yang sangat besar. Temukan berapa banyak yang Anda butuhkan dengan EXPLAIN ANALYZE
. Menyebutkan "Disk: " dalam langkah pengurutan menunjukkan perlunya lebih banyak:
- Parameter konfigurasi work_mem di PostgreSQL di Linux
- Optimalkan kueri sederhana menggunakan ORDER BY tanggal dan teks
Untuk banyak baris per pelanggan (kardinalitas rendah di kolom customer
), pemindaian indeks longgar (alias "lewati pemindaian") akan (jauh) lebih efisien, tetapi itu tidak diterapkan hingga Postgres 14. (Implementasi untuk pemindaian hanya indeks sedang dikembangkan untuk Postgres 15. Lihat di sini dan di sini.)
Untuk sekarang, ada teknik kueri yang lebih cepat untuk menggantikan ini. Khususnya jika Anda memiliki tabel terpisah yang menampung pelanggan unik, yang merupakan kasus penggunaan khas. Tetapi juga jika Anda tidak:
- SELECT DISTINCT lebih lambat dari yang diharapkan pada tabel saya di PostgreSQL
- Optimalkan kueri GROUP BY untuk mengambil baris terbaru per pengguna
- Optimalkan kueri maksimum berdasarkan grup
- Kueri N baris terkait terakhir per baris
Tolok Ukur
Lihat jawaban terpisah.