Vì vậy, bạn muốn nhận được hàng có cao nhất OrderField
mỗi nhóm? Tôi sẽ làm theo cách này:
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)
( CHỈNH SỬA bởi Tomas: Nếu có nhiều bản ghi có cùng OrderField trong cùng một nhóm và bạn cần chính xác một trong số chúng, bạn có thể muốn mở rộng điều kiện:
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
kết thúc chỉnh sửa.)
Nói cách khác, trả về hàng t1
mà không có hàng nào khác t2
tồn tại với cùng một GroupId
và lớn hơn OrderField
. Khi nào t2.*
là NULL, có nghĩa là kết nối bên ngoài bên trái không tìm thấy kết quả phù hợp nào như vậy và do đó t1
có giá trị lớn nhất OrderField
trong nhóm.
Không có cấp bậc, không có truy vấn phụ. Điều này sẽ chạy nhanh và tối ưu hóa quyền truy cập vào t2 với "Sử dụng chỉ mục" nếu bạn bật chỉ mục kết hợp (GroupId, OrderField)
.
Về hiệu suất, hãy xem câu trả lời của tôi cho Truy xuất bản ghi cuối cùng trong mỗi nhóm . Tôi đã thử một phương thức truy vấn con và phương thức kết hợp bằng cách sử dụng kết xuất dữ liệu Stack Overflow. Sự khác biệt là đáng chú ý: phương thức tham gia chạy nhanh hơn 278 lần trong thử nghiệm của tôi.
Điều quan trọng là bạn phải có chỉ số phù hợp để đạt được kết quả tốt nhất!
Về phương pháp của bạn bằng cách sử dụng biến @Rank, nó sẽ không hoạt động như bạn đã viết vì các giá trị của @Rank sẽ không đặt lại về 0 sau khi truy vấn đã xử lý bảng đầu tiên. Tôi sẽ chỉ cho bạn một ví dụ.
Tôi đã chèn một số dữ liệu giả, với một trường bổ sung là rỗng ngoại trừ trên hàng mà chúng tôi biết là lớn nhất cho mỗi nhóm:
select * from `Table`;
+---------+------------+------+
| GroupId | OrderField | foo |
+---------+------------+------+
| 10 | 10 | NULL |
| 10 | 20 | NULL |
| 10 | 30 | foo |
| 20 | 40 | NULL |
| 20 | 50 | NULL |
| 20 | 60 | foo |
+---------+------------+------+
Chúng tôi có thể cho thấy rằng xếp hạng tăng lên ba đối với nhóm đầu tiên và sáu đối với nhóm thứ hai và truy vấn bên trong trả về những thứ sau một cách chính xác:
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 |
+---------+---------+
Bây giờ hãy chạy truy vấn mà không có điều kiện nối, để buộc một tích Descartes của tất cả các hàng và chúng tôi cũng tìm nạp tất cả các cột:
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 |
+---------+---------+---------+------------+------+------+
Chúng ta có thể thấy ở trên rằng thứ hạng tối đa cho mỗi nhóm là đúng, nhưng sau đó @Rank tiếp tục tăng khi nó xử lý bảng dẫn xuất thứ hai, lên 7 và cao hơn. Vì vậy, các thứ hạng từ bảng dẫn xuất thứ hai sẽ không bao giờ trùng lặp với các hạng từ bảng dẫn xuất đầu tiên.
Bạn sẽ phải thêm một bảng dẫn xuất khác để buộc @Rank đặt lại về 0 giữa quá trình xử lý hai bảng (và hy vọng trình tối ưu hóa không thay đổi thứ tự mà nó đánh giá các bảng hoặc nếu không, hãy sử dụng STRAIGHT_JOIN để ngăn điều đó):
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 |
+---------+------------+------+------+
Nhưng sự tối ưu hóa của truy vấn này là khủng khiếp. Nó không thể sử dụng bất kỳ chỉ mục nào, nó tạo ra hai bảng tạm thời, sắp xếp chúng theo cách khó và thậm chí sử dụng bộ đệm tham gia vì nó cũng không thể sử dụng một chỉ mục khi tham gia các bảng tạm thời. Đây là ví dụ đầu ra từ 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 |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
Trong khi giải pháp của tôi bằng cách sử dụng nối ngoài bên trái sẽ tối ưu hóa tốt hơn nhiều. Nó không sử dụng bảng tạm thời và thậm chí cả các báo cáo "Using index"
, có nghĩa là nó có thể giải quyết kết nối chỉ bằng cách sử dụng chỉ mục mà không cần chạm vào dữ liệu.
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| 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 |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
Có thể bạn sẽ đọc những người đưa ra tuyên bố trên blog của họ rằng "tham gia làm cho SQL chậm", nhưng điều đó vô nghĩa. Tối ưu hóa kém làm cho SQL chậm.