Ada tabel messages
yang berisi data seperti yang ditunjukkan di bawah ini:
Id Name Other_Columns
-------------------------
1 A A_data_1
2 A A_data_2
3 A A_data_3
4 B B_data_1
5 B B_data_2
6 C C_data_1
Jika saya menjalankan kueri select * from messages group by name
, saya akan mendapatkan hasilnya sebagai:
1 A A_data_1
4 B B_data_1
6 C C_data_1
Permintaan apa yang akan mengembalikan hasil berikut?
3 A A_data_3
5 B B_data_2
6 C C_data_1
Artinya, catatan terakhir di setiap kelompok harus dikembalikan.
Saat ini, ini adalah permintaan yang saya gunakan:
SELECT
*
FROM (SELECT
*
FROM messages
ORDER BY id DESC) AS x
GROUP BY name
Tapi ini terlihat sangat tidak efisien. Adakah cara lain untuk mencapai hasil yang sama?
sql
mysql
group-by
greatest-n-per-group
Vijay Dev
sumber
sumber
Jawaban:
MySQL 8.0 sekarang mendukung fungsi windowing, seperti hampir semua implementasi SQL yang populer. Dengan sintaks standar ini, kita dapat menulis kueri terbesar-n-per-grup:
Di bawah ini adalah jawaban asli yang saya tulis untuk pertanyaan ini pada tahun 2009:
Saya menulis solusinya dengan cara ini:
Mengenai kinerja, satu solusi atau yang lain bisa lebih baik, tergantung pada sifat data Anda. Jadi, Anda harus menguji kedua kueri dan menggunakan salah satu yang lebih baik dalam kinerja mengingat database Anda.
Sebagai contoh, saya memiliki salinan dump data Agustus StackOverflow . Saya akan menggunakannya untuk pembandingan. Ada 1.114.357 baris dalam
Posts
tabel. Ini berjalan pada MySQL 5.0.75 di Macbook Pro 2.40GHz saya.Saya akan menulis kueri untuk menemukan posting terbaru untuk ID pengguna yang diberikan (milik saya).
Pertama menggunakan teknik yang ditunjukkan oleh @Eric dengan di
GROUP BY
dalam subquery:Bahkan
EXPLAIN
analisisnya memakan waktu lebih dari 16 detik:Sekarang hasilkan permintaan yang sama menggunakan teknik saya dengan
LEFT JOIN
:The
EXPLAIN
analisis menunjukkan bahwa kedua tabel dapat menggunakan indeks mereka:Inilah DDL untuk
Posts
tabel saya :sumber
<=
tidak akan membantu jika Anda memiliki kolom yang tidak unik. Anda harus menggunakan kolom unik sebagai tiebreak.UPD: 2017-03-31, versi 5.7.5 dari MySQL membuat sakelar ONLY_FULL_GROUP_BY diaktifkan secara default (karenanya, permintaan GROUP BY non-deterministik menjadi dinonaktifkan). Selain itu, mereka memperbarui implementasi GROUP BY dan solusinya mungkin tidak berfungsi seperti yang diharapkan bahkan dengan saklar yang dinonaktifkan. Orang perlu memeriksa.
Solusi Bill Karwin di atas bekerja dengan baik ketika jumlah item dalam kelompok agak kecil, tapi kinerja query menjadi buruk ketika kelompok-kelompok yang agak besar, karena solusinya membutuhkan sekitar
n*n/2 + n/2
hanyaIS NULL
perbandingan.Saya membuat tes pada tabel InnoDB
18684446
baris dengan1182
kelompok. Tabel berisi hasil tes untuk tes fungsional dan memiliki(test_id, request_id)
sebagai kunci utama. Jadi,test_id
adalah grup dan saya sedang mencari yang terakhirrequest_id
untuk masing-masingtest_id
.Solusi Bill telah berjalan selama beberapa jam di Dell e4310 saya dan saya tidak tahu kapan akan selesai meskipun beroperasi pada indeks cakupan (maka
using index
dalam EXPLAIN).Saya punya beberapa solusi lain yang didasarkan pada ide yang sama:
(group_id, item_value)
pasangan terbesar adalah nilai terakhir dalam masing-masinggroup_id
, itu adalah yang pertama untuk masing-masinggroup_id
jika kita berjalan melalui indeks dalam urutan menurun;3 cara MySQL menggunakan indeks adalah artikel yang bagus untuk memahami beberapa detail.
Solusi 1
Yang ini sangat cepat, butuh sekitar 0,8 detik pada baris 18M + saya:
Jika Anda ingin mengubah urutan menjadi ASC, masukkan ke dalam subquery, kembalikan id saja dan gunakan itu sebagai subquery untuk bergabung ke seluruh kolom:
Yang ini membutuhkan sekitar 1,2 detik pada data saya.
Solusi 2
Berikut ini solusi lain yang membutuhkan waktu sekitar 19 detik untuk meja saya:
Ini mengembalikan tes dalam urutan juga. Ini jauh lebih lambat karena melakukan pemindaian indeks penuh tetapi ada di sini untuk memberi Anda ide bagaimana untuk menghasilkan baris N max untuk setiap kelompok.
Kerugian dari kueri adalah bahwa hasilnya tidak dapat di-cache oleh cache kueri.
sumber
SELECT test_id, request_id FROM testresults GROUP BY test_id;
akan mengembalikan request_id minimum untuk setiap test_id.Gunakan subquery Anda untuk mengembalikan pengelompokan yang benar, karena Anda berada di tengah jalan.
Coba ini:
Jika tidak,
id
Anda ingin maks:Dengan cara ini, Anda menghindari subqueries yang berkorelasi dan / atau memesan di subqueries Anda, yang cenderung sangat lambat / tidak efisien.
sumber
other_col
: jika kolom itu tidak unik, Anda mungkin mendapatkan beberapa catatan kembali dengan yang samaname
, jika mereka mengikat untukmax(other_col)
. Saya menemukan posting ini yang menjelaskan solusi untuk kebutuhan saya, di mana saya membutuhkan tepat satu catatan pername
.INDEX(name, id)
danINDEX(name, other_col)
Saya sampai pada solusi yang berbeda, yaitu mendapatkan ID untuk posting terakhir dalam setiap grup, lalu pilih dari tabel pesan menggunakan hasil dari kueri pertama sebagai argumen untuk
WHERE x IN
konstruk:Saya tidak tahu bagaimana kinerjanya dibandingkan dengan beberapa solusi lain, tetapi ini bekerja secara spektakuler untuk meja saya dengan 3+ juta baris. (Eksekusi 4 detik dengan 1200+ hasil)
Ini harus berfungsi baik pada MySQL dan SQL Server.
sumber
Solusi oleh Sub query Link biola
Solusi Dengan bergabung dengan tautan biola kondisi
Alasan untuk posting ini adalah untuk memberikan tautan biola saja. SQL yang sama sudah disediakan di jawaban lain.
sumber
Pendekatan dengan kecepatan tinggi adalah sebagai berikut.
Hasil
sumber
id
dipesan sesuai kebutuhan Anda. Dalam kasus umum diperlukan beberapa kolom lain.Berikut ini dua saran. Pertama, jika mysql mendukung ROW_NUMBER (), itu sangat sederhana:
Saya mengasumsikan dengan "terakhir" yang Anda maksud terakhir dalam urutan Id. Jika tidak, ubah klausa ORDER BY dari jendela ROW_NUMBER (). Jika ROW_NUMBER () tidak tersedia, ini adalah solusi lain:
Kedua, jika tidak, ini sering merupakan cara yang baik untuk melanjutkan:
Dengan kata lain, pilih pesan di mana tidak ada pesan Id-nanti dengan Nama yang sama.
sumber
ROW_NUMBER()
dan CTE.Saya belum diuji dengan DB besar tapi saya pikir ini bisa lebih cepat daripada bergabung dengan tabel:
sumber
Berikut adalah cara lain untuk mendapatkan catatan terkait terakhir menggunakan
GROUP_CONCAT
dengan urutan oleh danSUBSTRING_INDEX
untuk memilih salah satu catatan dari daftarKueri di atas akan mengelompokkan semua
Other_Columns
yang ada dalamName
kelompok yang sama dan menggunakanORDER BY id DESC
akan bergabung dengan semuaOther_Columns
dalam grup tertentu dalam urutan menurun dengan pemisah yang disediakan dalam kasus saya yang telah saya gunakan||
, menggunakanSUBSTRING_INDEX
lebih dari daftar ini akan memilih yang pertamaDemo biola
sumber
group_concat_max_len
membatasi berapa banyak baris yang bisa Anda tangani.Jelas ada banyak cara berbeda untuk mendapatkan hasil yang sama, pertanyaan Anda sepertinya adalah cara yang efisien untuk mendapatkan hasil terakhir di setiap grup di MySQL. Jika Anda bekerja dengan data dalam jumlah besar dan menganggap Anda menggunakan InnoDB bahkan dengan versi terbaru MySQL (seperti 5.7.21 dan 8.0.4-rc) maka mungkin tidak ada cara yang efisien untuk melakukan ini.
Kita terkadang perlu melakukan ini dengan tabel dengan lebih dari 60 juta baris.
Untuk contoh-contoh ini saya akan menggunakan data dengan hanya sekitar 1,5 juta baris di mana kueri perlu menemukan hasil untuk semua grup dalam data. Dalam kasus kami yang sebenarnya, kami sering perlu mengembalikan data dari sekitar 2.000 kelompok (yang secara hipotetis tidak memerlukan pemeriksaan data yang sangat banyak).
Saya akan menggunakan tabel berikut:
Tabel suhu diisi dengan sekitar 1,5 juta catatan acak, dan dengan 100 kelompok berbeda. Terpilih_group diisi dengan 100 grup (dalam kasus kami ini biasanya akan kurang dari 20% untuk semua grup).
Karena data ini acak, ini berarti bahwa beberapa baris dapat memiliki catatanTestestamp yang sama. Yang kami inginkan adalah mendapatkan daftar semua grup yang dipilih dalam urutan groupID denganTimestamp yang direkam terakhir untuk setiap grup, dan jika grup yang sama memiliki lebih dari satu baris yang cocok seperti itu, maka id terakhir yang cocok dari baris tersebut.
Jika secara hipotesis MySQL memiliki fungsi terakhir () yang mengembalikan nilai dari baris terakhir dalam klausa ORDER BY khusus maka kita bisa melakukan:
yang hanya perlu memeriksa beberapa 100 baris dalam kasus ini karena tidak menggunakan fungsi GROUP BY yang normal. Ini akan dieksekusi dalam 0 detik dan karenanya sangat efisien. Perhatikan bahwa biasanya di MySQL kita akan melihat klausa ORDER BY mengikuti klausa GROUP BY namun klausa ORDER BY ini digunakan untuk menentukan ORDER untuk fungsi terakhir (), jika setelah GROUP BY maka akan memesan GROUPS. Jika tidak ada klausa GROUP BY yang hadir maka nilai terakhir akan sama di semua baris yang dikembalikan.
Namun MySQL tidak memiliki ini, jadi mari kita melihat ide-ide berbeda dari apa yang dimilikinya dan membuktikan bahwa tidak ada yang efisien.
Contoh 1
Ini memeriksa 3.009.254 baris dan mengambil ~ 0,859 detik pada 5.7.21 dan sedikit lebih lama pada 8.0.4-rc
Contoh 2
Ini memeriksa 1.505.331 baris dan mengambil ~ 1,25 detik pada 5.7.21 dan sedikit lebih lama pada 8.0.4-rc
Contoh 3
Ini memeriksa 3.009.685 baris dan mengambil ~ 1,95 detik pada 5.7.21 dan sedikit lebih lama pada 8.0.4-rc
Contoh 4
Ini memeriksa 6.137.810 baris dan memakan waktu ~ 2.2 detik pada 5.7.21 dan sedikit lebih lama pada 8.0.4-rc
Contoh 5
Ini memeriksa 6.017.808 baris dan memakan waktu ~ 4.2 detik pada 8.0.4-rc
Contoh 6
Ini memeriksa 6.017.908 baris dan mengambil ~ 17,5 detik pada 8.0.4-rc
Contoh 7
Yang ini memakan waktu lama jadi saya harus membunuhnya.
sumber
SELECT DISTINCT(groupID)
cepat dan akan memberi Anda semua data yang Anda butuhkan untuk membangun permintaan seperti itu. Anda harus baik-baik saja dengan ukuran permintaan selama tidak melebihimax_allowed_packet
, yang defaultnya adalah 4MB di MySQL 5.7.kita akan melihat bagaimana Anda dapat menggunakan MySQL untuk mendapatkan catatan terakhir dalam catatan Group By. Misalnya jika Anda memiliki set posting hasil ini.
id category_id post_title
1 1 Title 1
2 1 Title 2
3 1 Title 3
4 2 Title 4
5 2 Title 5
6 3 Title 6
Saya ingin bisa mendapatkan posting terakhir di setiap kategori yaitu Judul 3, Judul 5 dan Judul 6. Untuk mendapatkan posting berdasarkan kategori Anda akan menggunakan keyboard MySQL Group By.
select * from posts group by category_id
Tetapi hasil yang kami dapatkan dari permintaan ini adalah.
id category_id post_title
1 1 Title 1
4 2 Title 4
6 3 Title 6
Grup oleh akan selalu mengembalikan catatan pertama dalam grup pada hasil yang ditetapkan.
SELECT id, category_id, post_title FROM posts WHERE id IN ( SELECT MAX(id) FROM posts GROUP BY category_id );
Ini akan mengembalikan tulisan dengan ID tertinggi di setiap grup.
id category_id post_title
3 1 Title 3
5 2 Title 5
6 3 Title 6
Referensi Klik Di Sini
sumber
sumber
Inilah solusi saya:
sumber
SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME
.Coba ini:
sumber
Hai @Vijay Dev jika pesan tabel Anda berisi Id yang merupakan kunci primer kenaikan otomatis kemudian untuk mengambil basis catatan terbaru pada kunci utama kueri Anda harus membaca seperti di bawah ini:
sumber
Anda dapat melihat dari sini juga.
http://sqlfiddle.com/#!9/ef42b/9
SOLUSI PERTAMA
SOLUSI KEDUA
sumber
sumber
**
Hai, pertanyaan ini mungkin membantu:
**
sumber
Apakah ada cara kita bisa menggunakan metode ini untuk menghapus duplikat dalam sebuah tabel? Rangkaian hasil pada dasarnya adalah kumpulan catatan unik, jadi jika kami dapat menghapus semua catatan yang tidak ada dalam rangkaian hasil, kami akan secara efektif tidak memiliki duplikat? Saya mencoba ini tetapi mySQL memberikan kesalahan 1093.
Apakah ada cara untuk mungkin menyimpan output ke variabel temp lalu hapus dari NOT IN (variabel temp)? @Bill terima kasih atas solusi yang sangat berguna.
EDIT: Pikirkan saya menemukan solusinya:
sumber
Kueri di bawah ini akan berfungsi dengan baik sesuai pertanyaan Anda.
sumber
Jika Anda ingin baris terakhir untuk masing-masing
Name
, maka Anda dapat memberikan nomor baris untuk setiap grup baris denganName
dan memesan denganId
urutan menurun.PERTANYAAN
SQL Fiddle
sumber
Bagaimana dengan ini:
Saya memiliki masalah yang sama (pada postgresql tangguh) dan pada tabel catatan 1M. Solusi ini mengambil 1,7s vs 44s yang diproduksi oleh yang dengan LEFT JOIN. Dalam kasus saya, saya harus memfilter korrisponden bidang nama Anda terhadap nilai NULL, menghasilkan kinerja yang lebih baik lagi sebesar 0,2 detik
sumber
Jika kinerja benar-benar menjadi perhatian Anda, Anda dapat memperkenalkan kolom baru pada tabel yang disebut
IsLastInGroup
tipe BIT.Setel ke true pada kolom yang terakhir dan pertahankan dengan setiap baris masukkan / perbarui / hapus. Menulis akan lebih lambat, tetapi Anda akan mendapat manfaat saat membaca. Itu tergantung pada kasus penggunaan Anda dan saya sarankan hanya jika Anda fokus membaca
Jadi kueri Anda akan terlihat seperti:
sumber
sumber
Anda dapat mengelompokkan dengan menghitung dan juga mendapatkan item terakhir dari grup seperti:
sumber
Semoga di bawah ini permintaan Oracle dapat membantu:
sumber
Pendekatan lain:
Temukan propertie dengan harga m2_ max dengan setiap program (n properti dalam 1 program):
sumber