Seperti judulnya, saya ingin memilih baris pertama dari setiap set baris yang dikelompokkan dengan a GROUP BY
.
Khususnya, jika saya punya purchases
tabel yang terlihat seperti ini:
SELECT * FROM purchases;
Output saya:
id | pelanggan | total --- + ---------- + ------ 1 | Joe | 5 2 | Sally | 3 3 | Joe | 2 4 | Sally | 1
Saya ingin menanyakan id
tentang pembelian terbesar ( total
) yang dilakukan oleh masing-masing customer
. Sesuatu seperti ini:
SELECT FIRST(id), customer, FIRST(total)
FROM purchases
GROUP BY customer
ORDER BY total DESC;
Output yang Diharapkan:
PERTAMA (id) | pelanggan | PERTAMA (total) ---------- + ---------- + ------------- 1 | Joe | 5 2 | Sally | 3
sql
sqlite
postgresql
group-by
greatest-n-per-group
David Wolever
sumber
sumber
MAX(total)
?Jawaban:
Pada Oracle 9.2+ (bukan 8i + seperti yang dinyatakan sebelumnya), SQL Server 2005+, PostgreSQL 8.4+, DB2, Firebird 3.0+, Teradata, Sybase, Vertica:
Didukung oleh basis data apa pun:
Tetapi Anda perlu menambahkan logika untuk memutuskan hubungan:
sumber
ROW_NUMBER() OVER(PARTITION BY [...])
bersama dengan beberapa optimasi lainnya membantu saya menurunkan kueri dari 30 detik menjadi beberapa milidetik. Terima kasih! (PostgreSQL 9.2)total
untuk satu pelanggan, permintaan pertama mengembalikan pemenang yang arbitrer (tergantung pada detail implementasi;id
dapat berubah untuk setiap eksekusi!). Biasanya (tidak selalu) Anda ingin satu baris per pelanggan, ditentukan oleh kriteria tambahan seperti "yang dengan yang terkecilid
". Untuk memperbaikinya, tambahkanid
keORDER BY
daftarrow_number()
. Kemudian Anda mendapatkan hasil yang sama dengan kueri ke - 2 , yang sangat tidak efisien untuk kasus ini. Selain itu, Anda memerlukan subquery lain untuk setiap kolom tambahan.Dalam PostgreSQL ini biasanya lebih sederhana dan lebih cepat (lebih banyak optimasi kinerja di bawah):
Atau lebih pendek (jika tidak sejelas) dengan nomor urut kolom output:
Jika
total
bisa NULL (tidak ada salahnya, tetapi Anda ingin mencocokkan indeks yang ada ):Poin utama
DISTINCT ON
adalah ekstensi PostgreSQL dari standar (di mana hanyaDISTINCT
pada seluruhSELECT
daftar didefinisikan).Daftar sejumlah ekspresi dalam
DISTINCT ON
klausa, nilai baris gabungan mendefinisikan duplikat. Manual:Penekanan berani saya.
DISTINCT ON
dapat dikombinasikan denganORDER BY
. Ekspresi terkemuka diORDER BY
harus dalam rangkaian ekspresiDISTINCT ON
, tetapi Anda dapat mengatur ulang urutan di antara mereka secara bebas. Contoh. Anda dapat menambahkan ekspresi tambahanORDER BY
untuk memilih baris tertentu dari setiap grup rekan. Atau, seperti yang dikatakan manual :Saya menambahkan
id
sebagai item terakhir untuk memutuskan hubungan:"Pilih baris dengan yang terkecil
id
dari masing-masing kelompok yang berbagi tertinggitotal
."Untuk memesan hasil dengan cara yang tidak setuju dengan urutan yang menentukan yang pertama per grup, Anda dapat membuat sarang permintaan di atas dalam permintaan luar dengan yang lain
ORDER BY
. Contoh.Jika
total
bisa NULL, Anda kemungkinan besar menginginkan baris dengan nilai bukan nol terbesar. TambahkanNULLS LAST
seperti yang ditunjukkan. Lihat:The
SELECT
daftar tidak dibatasi oleh ekspresi dalamDISTINCT ON
atauORDER BY
dengan cara apapun. (Tidak diperlukan dalam kasus sederhana di atas):Anda tidak harus memasukkan ekspresi apa pun di
DISTINCT ON
atauORDER BY
.Anda dapat memasukkan ekspresi lain dalam
SELECT
daftar. Ini penting untuk mengganti permintaan yang jauh lebih kompleks dengan fungsi subqueries dan agregat / jendela.Saya diuji dengan Postgres versi 8.3 - 12. Tetapi fitur tersebut sudah ada setidaknya sejak versi 7.1, jadi pada dasarnya selalu.
Indeks
The sempurna indeks untuk query di atas akan menjadi indeks multi-kolom yang mencakup semua tiga kolom dalam pencocokan urutan dan dengan pencocokan urutan:
Mungkin terlalu khusus. Tetapi gunakan itu jika membaca kinerja untuk permintaan tertentu sangat penting. Jika ada
DESC NULLS LAST
dalam kueri, gunakan yang sama dalam indeks sehingga urutan sortir cocok dan indeks berlaku.Efektivitas / Optimalisasi kinerja
Timbang biaya dan manfaat sebelum membuat indeks khusus untuk setiap permintaan. Potensi indeks di atas sangat tergantung pada distribusi data .
Indeks digunakan karena memberikan data yang diurutkan. Di Postgres 9.2 atau yang lebih baru, kueri juga dapat memanfaatkan pemindaian hanya indeks jika indeks lebih kecil dari tabel yang mendasarinya. Indeks harus dipindai secara keseluruhan.
Untuk beberapa baris per pelanggan (kardinalitas tinggi dalam kolom
customer
), ini sangat efisien. Terlebih lagi jika Anda membutuhkan output yang diurutkan. Keuntungan menyusut dengan semakin banyak baris per pelanggan.Idealnya, Anda memiliki cukup
work_mem
untuk memproses langkah sortir yang terlibat dalam RAM dan tidak tumpah ke disk. Tetapi umumnya pengaturanwork_mem
terlalu tinggi dapat memiliki efek buruk. PertimbangkanSET LOCAL
untuk pertanyaan yang sangat besar. Temukan berapa banyak yang Anda butuhkanEXPLAIN ANALYZE
. Sebutkan " Disk: " pada langkah sortir yang menunjukkan perlunya lebih banyak:Untuk banyak baris per pelanggan (kardinalitas rendah di kolom
customer
), pemindaian indeks longgar (alias "lompati pemindaian") akan (jauh) lebih efisien, tetapi itu tidak diterapkan hingga Postgres 12. (Implementasi untuk pemindaian hanya indeks adalah dalam pengembangan untuk Postgres 13. Lihat di sini dan di sini .)Untuk saat ini, ada teknik query yang lebih cepat untuk menggantikan ini. Khususnya jika Anda memiliki meja terpisah yang menampung pelanggan unik, yang merupakan kasus penggunaan umum. Tetapi juga jika Anda tidak:
Tolok ukur
Saya memiliki patokan sederhana di sini yang sudah usang sekarang. Saya menggantinya dengan patokan terperinci dalam jawaban terpisah ini .
sumber
DISTINCT ON
menjadi sangat lambat. Implementasi selalu mengurutkan seluruh tabel dan memindai untuk duplikat, mengabaikan semua indeks (bahkan jika Anda telah membuat indeks multi-kolom yang diperlukan). Lihat answerextended.com/2009/05/03/postgresql-optimizing-distinct untuk solusi yang memungkinkan.SELECT
daftar.DISTINCT ON
hanya baik untuk mendapatkan satu baris per grup rekan.Tolok ukur
Menguji kandidat yang paling menarik dengan Postgres 9.4 dan 9.5 dengan meja setengah realistis 200k baris di
purchases
dan 10k yang berbedacustomer_id
( rata-rata. 20 baris per pelanggan ).Untuk Postgres 9.5 saya menjalankan tes ke-2 dengan 86446 pelanggan yang berbeda secara efektif. Lihat di bawah ( rata-rata 2.3 baris per pelanggan ).
Mendirikan
Meja utama
Saya menggunakan
serial
(batasan PK yang ditambahkan di bawah) dan bilangan bulatcustomer_id
karena itu pengaturan yang lebih umum. Juga ditambahkansome_column
untuk menebus kolom biasanya lebih banyak.Data dummy, PK, indeks - tabel khas juga memiliki beberapa tupel mati:
customer
tabel - untuk kueri superiorDalam pengujian kedua saya untuk 9,5 saya menggunakan setup yang sama, tetapi dengan
random() * 100000
menghasilkancustomer_id
hanya beberapa baris percustomer_id
.Ukuran objek untuk tabel
purchases
Dihasilkan dengan kueri ini .
Pertanyaan
1.
row_number()
di CTE, ( lihat jawaban lain )2.
row_number()
di subquery (optimasi saya)3.
DISTINCT ON
( lihat jawaban lain )4. rCTE dengan
LATERAL
subquery ( lihat di sini )5.
customer
tabel denganLATERAL
( lihat di sini )6.
array_agg()
denganORDER BY
( lihat jawaban lain )Hasil
Waktu eksekusi untuk kueri di atas dengan
EXPLAIN ANALYZE
(dan semua opsi tidak aktif ), terbaik dari 5 berjalan .Semua pertanyaan menggunakan Pemindaian Hanya Indeks aktif
purchases2_3c_idx
(di antara langkah-langkah lain). Beberapa dari mereka hanya untuk ukuran indeks yang lebih kecil, yang lain lebih efektif.A. Postgres 9.4 dengan 200.000 baris dan ~ 20 per
customer_id
B. Sama dengan Postgres 9.5
C. Sama seperti B., tetapi dengan ~ 2,3 baris per
customer_id
Tolok ukur terkait
Inilah yang baru dengan pengujian "ogr" dengan baris 10M dan "pelanggan" 60r unik di Postgres 11.5 (saat ini pada September 2019). Hasil masih sejalan dengan apa yang telah kita lihat sejauh ini:
Benchmark asli (kedaluwarsa) dari 2011
Saya menjalankan tiga tes dengan PostgreSQL 9.1 pada tabel kehidupan nyata dari 65579 baris dan indeks btree satu kolom pada masing-masing dari tiga kolom yang terlibat dan mengambil waktu eksekusi terbaik dari 5 berjalan.
Membandingkan permintaan pertama @OMGPonies (
A
) dengan solusi di atasDISTINCT ON
(B
):Pilih seluruh tabel, hasil dalam 5958 baris dalam kasus ini.
Gunakan kondisi yang
WHERE customer BETWEEN x AND y
menghasilkan 1000 baris.Pilih satu pelanggan dengan
WHERE customer = x
.Tes yang sama diulang dengan indeks yang dijelaskan dalam jawaban lainnya
sumber
2. row_number()
dan5. customer table with LATERAL
contoh, apa yang memastikan id akan menjadi yang terkecil?customer_id
baris dengan yang tertinggitotal
. Ini adalah kebetulan yang menyesatkan dalam data uji dari pertanyaan bahwaid
dalam baris yang dipilih kebetulan juga menjadi per terkecilcustomer_id
.Ini biasa terbesar-n-per-kelompokmasalah, yang sudah memiliki solusi yang teruji dan sangat optimal . Secara pribadi saya lebih suka solusi join kiri oleh Bill Karwin ( posting asli dengan banyak solusi lain ).
Perhatikan bahwa banyak solusi untuk masalah umum ini secara mengejutkan dapat ditemukan di salah satu sumber paling resmi, manual MySQL ! Lihat Contoh Permintaan Umum :: Baris Memegang Maksimum Grup dari Kolom Tertentu .
sumber
DISTINCT ON
versi ini jauh lebih pendek, lebih sederhana dan umumnya berkinerja lebih baik di Postgres daripada alternatif dengan selfLEFT JOIN
atau semi-anti-joinNOT EXISTS
. Ini juga "diuji dengan baik".Di Postgres, Anda dapat menggunakan
array_agg
seperti ini:Ini akan memberi Anda
id
pembelian terbesar setiap pelanggan.Beberapa hal yang perlu diperhatikan:
array_agg
adalah fungsi agregat, jadi itu berfungsi denganGROUP BY
.array_agg
memungkinkan Anda menentukan pemesanan yang mencakup hanya untuk dirinya sendiri, sehingga tidak membatasi struktur dari keseluruhan kueri. Ada juga sintaks untuk bagaimana Anda mengurutkan NULLs, jika Anda perlu melakukan sesuatu yang berbeda dari default.array_agg
cara serupa untuk kolom output ketiga Anda, tetapimax(total)
lebih sederhana.DISTINCT ON
menggunakan menggunakanarray_agg
memungkinkan Anda menyimpannyaGROUP BY
, jika Anda menginginkannya karena alasan lain.sumber
Solusinya tidak terlalu efisien seperti yang ditunjukkan oleh Erwin, karena keberadaan SubQ
sumber
Saya menggunakan cara ini (hanya postgresql): https://wiki.postgresql.org/wiki/First/last_%28aggregate%29
Maka contoh Anda harus bekerja hampir seperti:
CAVEAT: Itu mengabaikan baris NULL
Sunting 1 - Gunakan ekstensi postgres sebagai gantinya
Sekarang saya menggunakan cara ini: http://pgxn.org/dist/first_last_agg/
Untuk menginstal di ubuntu 14.04:
Ini adalah ekstensi postgres yang memberi Anda fungsi pertama dan terakhir; ternyata lebih cepat dari cara di atas.
Sunting 2 - Memesan dan memfilter
Jika Anda menggunakan fungsi agregat (seperti ini), Anda dapat memesan hasilnya, tanpa harus memiliki data yang sudah dipesan:
Jadi contoh yang setara, dengan pemesanan akan menjadi sesuatu seperti:
Tentu saja Anda dapat memesan dan memfilter sesuai dengan agregat; itu sintaks yang sangat kuat.
sumber
Pertanyaan:
BAGAIMANA CARA KERJANYA! (Aku pernah disana)
Kami ingin memastikan bahwa kami hanya memiliki total tertinggi untuk setiap pembelian.
Beberapa Hal Teoritis (lewati bagian ini jika Anda hanya ingin memahami permintaan)
Biarkan Total menjadi fungsi T (pelanggan, id) di mana ia mengembalikan nilai yang diberikan nama dan id Untuk membuktikan bahwa total yang diberikan (T (pelanggan, id)) adalah yang tertinggi kita harus membuktikan bahwa Kami ingin membuktikan
ATAU
Pendekatan pertama akan membutuhkan kita untuk mendapatkan semua catatan untuk nama yang tidak terlalu saya sukai.
Yang kedua akan membutuhkan cara yang cerdas untuk mengatakan tidak ada catatan yang lebih tinggi dari yang ini.
Kembali ke SQL
Jika kita pergi, gabungkan tabel dengan nama dan totalnya kurang dari tabel yang digabungkan:
kami memastikan bahwa semua catatan yang memiliki catatan lain dengan total lebih tinggi untuk pengguna yang sama untuk bergabung:
Itu akan membantu kami memfilter untuk total tertinggi untuk setiap pembelian tanpa pengelompokan yang diperlukan:
Dan itulah jawaban yang kita butuhkan.
sumber
Solusi yang sangat cepat
dan sangat cepat jika tabel diindeks oleh id:
sumber
Di SQL Server Anda bisa melakukan ini:
Penjelasan: Here Group by dilakukan berdasarkan pelanggan dan kemudian memesannya secara total maka masing-masing grup tersebut diberi nomor seri sebagai StRank dan kami mengeluarkan 1 pelanggan pertama dengan StRank 1
sumber
Gunakan
ARRAY_AGG
fungsi untuk PostgreSQL , U-SQL , IBM DB2 , dan Google BigQuery SQL :sumber
Di PostgreSQL, kemungkinan lain adalah menggunakan
first_value
fungsi jendela dalam kombinasi denganSELECT DISTINCT
:Saya membuat komposit
(id, total)
, jadi kedua nilai dikembalikan oleh agregat yang sama. Tentu saja Anda selalu dapat menerapkanfirst_value()
dua kali.sumber
Solusi OMG Ponies "Didukung oleh basis data apa pun" yang diterima memiliki kecepatan yang baik dari pengujian saya.
Di sini saya memberikan pendekatan yang sama, tetapi solusi database apa pun lebih lengkap dan bersih. Dasi dipertimbangkan (anggap keinginan untuk mendapatkan hanya satu baris untuk setiap pelanggan, bahkan beberapa catatan untuk jumlah maksimum per pelanggan), dan bidang pembelian lainnya (mis. Pembelian_payment_id) akan dipilih untuk baris yang benar-benar cocok di tabel pembelian.
Didukung oleh basis data apa pun:
Permintaan ini cukup cepat terutama ketika ada indeks komposit seperti (pelanggan, total) pada tabel pembelian.
Ucapan:
t1, t2 adalah alias subquery yang dapat dihapus tergantung pada database.
Peringatan :
using (...)
klausa saat ini tidak didukung dalam MS-SQL dan Oracle db pada edit ini pada Januari 2017. Anda harus mengembangkannya sendiri ke mison t2.id = purchase.id
. Dll. Sintaks PENGGUNAAN bekerja dalam SQLite, MySQL dan PostgreSQL.sumber
Snowflake / Teradata mendukung
QUALIFY
klausa yang berfungsi sepertiHAVING
untuk fungsi berjendela:sumber
Jika Anda ingin memilih baris apa pun (berdasarkan kondisi spesifik Anda) dari kumpulan baris teragregasi.
Jika Anda ingin menggunakan
sum/avg
fungsi agregasi ( ) lainnya sebagai tambahanmax/min
. Dengan demikian Anda tidak dapat menggunakan petunjukDISTINCT ON
Anda dapat menggunakan subquery berikutnya:
Anda bisa mengganti
amount = MAX( tf.amount )
dengan kondisi apa pun yang Anda inginkan dengan satu batasan: Subquery ini tidak boleh mengembalikan lebih dari satu barisTetapi jika Anda ingin melakukan hal-hal seperti itu, Anda mungkin mencari fungsi jendela
sumber
Untuk SQl Server cara yang paling efisien adalah:
dan jangan lupa untuk membuat indeks berkerumun untuk kolom yang digunakan
sumber