Jadi, Anda ingin mendapatkan baris dengan OrderField
tertinggi per kelompok? 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 membutuhkan salah satunya, Anda mungkin ingin memperpanjang kondisinya:
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 ada baris lain t2
ada dengan GroupId
yang sama dan OrderField
greater yang lebih besar . Ketika t2.*
adalah NULL, itu berarti gabungan luar kiri tidak menemukan kecocokan seperti itu, dan karena itu t1
memiliki nilai terbesar OrderField
dalam kelompok.
Tidak ada peringkat, tidak ada subquery. Ini harus berjalan cepat dan mengoptimalkan akses ke t2 dengan "Menggunakan indeks" jika Anda memiliki indeks gabungan di (GroupId, OrderField)
.
Mengenai kinerja, lihat jawaban saya untuk Mengambil catatan terakhir di setiap grup . Saya mencoba metode subquery dan metode join menggunakan data dump Stack Overflow. Perbedaannya luar biasa:metode join berjalan 278 kali lebih cepat dalam pengujian saya.
Anda harus memiliki indeks yang tepat untuk mendapatkan hasil terbaik!
Mengenai metode Anda menggunakan variabel @Rank, itu tidak akan berfungsi seperti yang Anda tulis, karena nilai @Rank tidak akan direset ke nol setelah kueri memproses tabel pertama. Saya akan menunjukkan sebuah contoh.
Saya memasukkan beberapa data dummy, dengan bidang tambahan yang nol kecuali pada baris yang kita 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 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 bergabung, untuk memaksa produk Cartesian 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
-- on t.GroupId = s.GroupId and t.MaxRank = s.Rank
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 atas bahwa peringkat maksimal per grup sudah benar, tetapi kemudian @Rank terus meningkat saat memproses tabel turunan kedua, ke 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 mengatur 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 -- RESET @Rank TO ZERO HERE
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 bergabung dengan tabel temp. Ini adalah contoh output 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. Itu tidak menggunakan tabel temp dan bahkan melaporkan "Using index"
yang berarti dapat menyelesaikan penggabungan hanya dengan menggunakan indeks, tanpa menyentuh data.
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| 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 lambat," tapi itu omong kosong. Pengoptimalan yang buruk membuat SQL menjadi lambat.