Jadi Anda ingin mendapatkan baris dengan jumlah tertinggi OrderField
per grup? Saya akan melakukannya dengan cara ini:
SELECT t1.*
FROM `Table` AS t1
LEFT OUTER JOIN `Table` AS t2
ON t1.GroupId = t2.GroupId AND t1.OrderField < t2.OrderField
WHERE t2.GroupId IS NULL
ORDER BY t1.OrderField; // not needed! (note by Tomas)
( EDIT oleh Tomas: Jika ada lebih banyak record dengan OrderField yang sama dalam grup yang sama dan Anda memerlukan tepat salah satunya, Anda mungkin ingin memperpanjang kondisi:
SELECT t1.*
FROM `Table` AS t1
LEFT OUTER JOIN `Table` AS t2
ON t1.GroupId = t2.GroupId
AND (t1.OrderField < t2.OrderField
OR (t1.OrderField = t2.OrderField AND t1.Id < t2.Id))
WHERE t2.GroupId IS NULL
akhir pengeditan.)
Dengan kata lain, kembalikan baris t1
yang tidak t2
ada baris lain yang sama GroupId
dan lebih besar OrderField
. Ketika t2.*
NULL, itu berarti left outer join tidak menemukan kecocokan tersebut, dan karena itu t1
memiliki nilai terbesar OrderField
dalam grup.
Tidak ada peringkat, tidak ada subkueri. Ini akan berjalan cepat dan mengoptimalkan akses ke t2 dengan "Menggunakan indeks" jika Anda mengaktifkan indeks gabungan (GroupId, OrderField)
.
Mengenai kinerja, lihat jawaban saya untuk Mengambil rekor terakhir di setiap grup . Saya mencoba metode subquery dan metode bergabung menggunakan dump data Stack Overflow. Perbedaannya luar biasa: metode bergabung berjalan 278 kali lebih cepat dalam pengujian saya.
Penting bagi Anda untuk memiliki indeks yang tepat untuk mendapatkan hasil terbaik!
Mengenai metode Anda yang menggunakan variabel @Rank, ini tidak akan berfungsi seperti yang Anda tulis, karena nilai @Rank tidak akan disetel ulang ke nol setelah kueri memproses tabel pertama. Saya akan tunjukkan sebuah contoh.
Saya memasukkan beberapa data dummy, dengan bidang tambahan yang nol kecuali pada baris yang kami tahu adalah yang terbesar per grup:
select * from `Table`;
+
| GroupId | OrderField | foo |
+
| 10 | 10 | NULL |
| 10 | 20 | NULL |
| 10 | 30 | foo |
| 20 | 40 | NULL |
| 20 | 50 | NULL |
| 20 | 60 | foo |
+
Kami dapat menunjukkan bahwa peringkat meningkat menjadi tiga untuk grup pertama dan enam untuk grup kedua, dan kueri bagian dalam mengembalikan ini dengan benar:
select GroupId, max(Rank) AS MaxRank
from (
select GroupId, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField) as t
group by GroupId
+
| GroupId | MaxRank |
+
| 10 | 3 |
| 20 | 6 |
+
Sekarang jalankan kueri tanpa kondisi gabungan, untuk memaksa produk Kartesius dari semua baris, dan kami juga mengambil semua kolom:
select s.*, t.*
from (select GroupId, max(Rank) AS MaxRank
from (select GroupId, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField
) as t
group by GroupId) as t
join (
select *, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField
) as s
order by OrderField;
+
| GroupId | MaxRank | GroupId | OrderField | foo | Rank |
+
| 10 | 3 | 10 | 10 | NULL | 7 |
| 20 | 6 | 10 | 10 | NULL | 7 |
| 10 | 3 | 10 | 20 | NULL | 8 |
| 20 | 6 | 10 | 20 | NULL | 8 |
| 20 | 6 | 10 | 30 | foo | 9 |
| 10 | 3 | 10 | 30 | foo | 9 |
| 10 | 3 | 20 | 40 | NULL | 10 |
| 20 | 6 | 20 | 40 | NULL | 10 |
| 10 | 3 | 20 | 50 | NULL | 11 |
| 20 | 6 | 20 | 50 | NULL | 11 |
| 20 | 6 | 20 | 60 | foo | 12 |
| 10 | 3 | 20 | 60 | foo | 12 |
+
Kita dapat melihat dari penjelasan di atas bahwa peringkat maksimal per grup sudah benar, tetapi @Rank terus meningkat saat memproses tabel turunan kedua, menjadi 7 dan lebih tinggi. Jadi peringkat dari tabel turunan kedua tidak akan pernah tumpang tindih dengan peringkat dari tabel turunan pertama sama sekali.
Anda harus menambahkan tabel turunan lain untuk memaksa @Rank disetel ulang ke nol di antara pemrosesan dua tabel (dan berharap pengoptimal tidak mengubah urutan evaluasi tabel, atau gunakan STRAIGHT_JOIN untuk mencegahnya):
select s.*
from (select GroupId, max(Rank) AS MaxRank
from (select GroupId, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField
) as t
group by GroupId) as t
join (select @Rank := 0) r
join (
select *, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField
) as s
on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField;
+
| GroupId | OrderField | foo | Rank |
+
| 10 | 30 | foo | 3 |
| 20 | 60 | foo | 6 |
+
Tetapi pengoptimalan kueri ini sangat buruk. Itu tidak dapat menggunakan indeks apa pun, itu membuat dua tabel sementara, mengurutkannya dengan cara yang sulit, dan bahkan menggunakan buffer gabungan karena tidak dapat menggunakan indeks saat menggabungkan tabel temp juga. Ini adalah contoh keluaran dari EXPLAIN
:
+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+
| 1 | PRIMARY | <derived4> | system | NULL | NULL | NULL | NULL | 1 | Using temporary; Using filesort |
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 2 | |
| 1 | PRIMARY | <derived5> | ALL | NULL | NULL | NULL | NULL | 6 | Using where; Using join buffer |
| 5 | DERIVED | Table | ALL | NULL | NULL | NULL | NULL | 6 | Using filesort |
| 4 | DERIVED | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
| 2 | DERIVED | <derived3> | ALL | NULL | NULL | NULL | NULL | 6 | Using temporary; Using filesort |
| 3 | DERIVED | Table | ALL | NULL | NULL | NULL | NULL | 6 | Using filesort |
+
Sedangkan solusi saya menggunakan gabungan luar kiri mengoptimalkan jauh lebih baik. Ini tidak menggunakan tabel temp dan bahkan laporan "Using index"
yang berarti dapat menyelesaikan gabungan hanya menggunakan indeks, tanpa menyentuh datanya.
+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+
| 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 6 | Using filesort |
| 1 | SIMPLE | t2 | ref | GroupId | GroupId | 5 | test.t1.GroupId | 1 | Using where; Using index |
+
Anda mungkin akan membaca orang yang membuat klaim di blog mereka bahwa "bergabung membuat SQL menjadi lambat," tapi itu tidak masuk akal. Pengoptimalan yang buruk membuat SQL menjadi lambat.