2012-01-05 37 views
67

Làm cách nào để thực hiện điều đó?Nhận hồ sơ có số lượng cao nhất/nhỏ nhất <whatever> cho mỗi nhóm

Cựu tiêu đề của câu hỏi này là "sử dụng cấp bậc (@Rank: = @Rank + 1) trong truy vấn phức tạp với các truy vấn con - nó sẽ làm việc?" Bởi vì tôi đang tìm kiếm giải pháp sử dụng cấp bậc, nhưng bây giờ tôi thấy rằng giải pháp được đăng bởi Bill là tốt hơn nhiều.

câu hỏi gốc:

Tôi đang cố gắng để soạn một truy vấn mà sẽ mất kỷ lục cuối cùng từ mỗi nhóm đưa ra một số trình tự quy định:

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 

Biểu @Rank := @Rank + 1 thường được sử dụng để xếp hạng, nhưng đối với tôi có vẻ đáng ngờ khi được sử dụng trong 2 truy vấn con, nhưng chỉ được khởi tạo một lần. Nó sẽ hoạt động theo cách này?

Và thứ hai, nó có hoạt động với một truy vấn phụ được đánh giá nhiều lần không? Giống như truy vấn phụ trong mệnh đề (hoặc có) (cách khác để viết nội dung trên):

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 

Cảm ơn bạn trước!

+1

câu hỏi nâng cao hơn tại đây http://stackoverflow.com/questions/9841093/how-to-writegreatest-n-per-group-type-query-but-with-additional-conditions/9845109#9845109 – TMS

Trả lời

129

Vì vậy, bạn muốn nhận hàng có mức cao nhất là OrderField cho mỗi nhóm? Tôi muốn làm điều đó 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) 

(EDIT bởi Tomas: Nếu có nhiều hồ sơ với các OrderField giống nhau trong cùng một nhóm và bạn cần phải chính xác một trong số họ, 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 

cuối chỉnh sửa)

Nói cách khác, trả lại hàng t1 mà không có hàng khác t2 tồn tại với cùng GroupId và lớn hơn.. Khi t2.* là NULL, nó có nghĩa là bên ngoài tham gia bên ngoài không tìm thấy trận đấu như vậy, và do đó t1 có giá trị lớn nhất của OrderField trong nhóm.

Không có thứ hạng, 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 bằng "Sử dụng chỉ mục" nếu bạn có chỉ mục phức hợp trên (GroupId, OrderField).


Về hiệu suất, hãy xem câu trả lời của tôi cho Retrieving the last record in each group. Tôi đã thử một phương thức truy vấn con và phương thức kết nối 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 pháp kết nối chạy nhanh hơn 278 lần trong thử nghiệm của tôi.

Điều quan trọng là bạn có đúng chỉ mục để có kết quả tốt nhất!

Về phương pháp của bạn bằng biến @Rank, nó sẽ không hoạt động như bạn đã viết, bởi vì các giá trị của @Rank sẽ không được đặt lại về 0 sau khi truy vấn đã xử lý bảng đầu tiên. Tôi sẽ cho bạn xem một ví dụ.

tôi chèn một số dữ liệu giả, với trường bổ sung mà là null trừ trên hàng chúng ta 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 ta có thể thấy rằng cấp bậc tăng lên ba cho nhóm đầu tiên và sáu cho nhóm thứ hai, và truy vấn nội trả về những 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ờ chạy các truy vấn không có điều kiện join, để buộc một sản phẩm Descartes của tất cả các hàng, và chúng tôi cũng lấy 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 từ trên rằng xếp hạng tối đa cho mỗi nhóm là chính xác, 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 cấp bậc từ bảng dẫn xuất thứ hai sẽ không bao giờ chồng lên nhau với các cấp bậc 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 bằng 0 giữa hai bảng (và hy vọng trình tối ưu hóa không thay đổi thứ tự mà bảng đánh giá bảng hoặc sử dụng STRAIGHT_JOIN để ngăn chặn rằng):

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 tối ưu hó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 cứng và thậm chí sử dụng bộ đệm nối vì nó không thể sử dụng 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 sử dụng phép nối ngoài bên trái tối ưu hóa tốt hơn nhiều. Nó sử dụng không có bảng tạm thời và thậm chí báo cáo "Using index" có nghĩa là nó có thể giải quyết việc tham gia bằng cách sử dụng chỉ số 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 khiếu nại trên blog của họ rằng "tham gia tạo SQL chậm", nhưng điều đó là vô nghĩa. Tối ưu hóa kém làm cho SQL chậm.

+0

Điều này có thể chứng minh khá hữu ích (cho OP quá), nhưng, thật đáng buồn, câu trả lời không phải của hai câu hỏi. –

+0

Cảm ơn Bill, đó là một ý tưởng tốt để tránh các cấp bậc, nhưng ... sẽ không tham gia được làm chậm? Việc tham gia (không có giới hạn điều khoản ở đâu) sẽ có kích thước lớn hơn nhiều so với các truy vấn của tôi. Dù sao, cảm ơn cho ý tưởng! Nhưng tôi cũng sẽ rất thú vị trong câu hỏi ban đầu, tức là nếu các cấp bậc sẽ hoạt động theo cách này. – TMS

+0

Cảm ơn câu trả lời tuyệt vời, Bill. Tuy nhiên, điều gì sẽ xảy ra nếu tôi sử dụng '@ Rank1' và' @ Rank2', một cho mỗi truy vấn phụ? Điều đó có khắc phục được sự cố không? Điều đó có nhanh hơn giải pháp của bạn không? – TMS

Các vấn đề liên quan