Bagaimana cara melakukannya?
Judul sebelumnya dari pertanyaan ini adalah " menggunakan peringkat (@Rank: = @Rank + 1) dalam kueri kompleks dengan subkueri - apakah akan berhasil? " Karena saya sedang mencari solusi menggunakan peringkat, tetapi sekarang saya melihat bahwa solusi yang diposting oleh Bill adalah jauh lebih baik.
Pertanyaan asli:
Saya mencoba membuat kueri yang akan mengambil catatan terakhir dari setiap grup dengan beberapa urutan yang ditentukan:
SET @Rank=0;
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 := @Rank + 1 AS Rank
from Table
order by OrderField
) as s
on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField
Ekspresi @Rank := @Rank + 1
biasanya digunakan untuk peringkat, tetapi bagi saya terlihat mencurigakan saat digunakan dalam 2 subkueri, tetapi hanya diinisialisasi sekali. Apakah akan bekerja seperti ini?
Dan kedua, apakah ini akan bekerja dengan satu subkueri yang dievaluasi beberapa kali? Seperti subquery di mana (atau memiliki) klausa (cara lain untuk menulis di atas):
SET @Rank=0;
select Table.*, @Rank := @Rank + 1 AS Rank
from Table
having Rank = (select max(Rank) AS MaxRank
from (select GroupId, @Rank := @Rank + 1 AS Rank
from Table as t0
order by OrderField
) as t
where t.GroupId = table.GroupId
)
order by OrderField
Terima kasih sebelumnya!
Jawaban:
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 tidakt2
ada baris lain yang samaGroupId
dan lebih besarOrderField
. Ketikat2.*
NULL, itu berarti left outer join tidak menemukan kecocokan tersebut, dan karena itut1
memiliki nilai terbesarOrderField
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 -- 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 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 -- 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 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.
sumber
@Rank1
dan@Rank2
, satu untuk setiap subkueri? Akankah itu menyelesaikan masalah? Apakah itu lebih cepat dari solusi Anda?@Rank1
dan tidak@Rank2
akan membuat perbedaan.... AND t1.foo = t2.foo
WHERE ... AND foo='bar'