Mysql
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Mysql

Nhận các bản ghi với cao nhất / nhỏ nhất cho mỗi nhóm

Vì vậy, bạn muốn lấy hàng có OrderField cao nhất 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)

( EDIT 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 không có hàng nào khác t2 tồn tại với cùng một GroupIdOrderField lớn hơn . Khi t2.* là NULL, có nghĩa là không tìm thấy kết nối bên ngoài bên trái nào khớp như vậy và do đó t1 có giá trị lớn nhất là 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 có chỉ mục kết hợp trên (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 pháp 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ỉ mục phù hợp để có đượ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 không có giá trị 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 cho nhóm đầu tiên và sáu cho nhóm thứ hai và truy vấn bên trong trả về các giá trị này 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 |
+---------+---------+---------+------------+------+------+

Ở trên, chúng ta có thể thấy rằng xếp 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, 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 chỉ mục khi tham gia các bảng tạm thời. Đây là kết quả mẫu 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 phép nối bên 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 báo cáo "Using index" có nghĩa là nó có thể giải quyết liên kết 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.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. LỖI 2006 (HY000):Máy chủ MySQL đã biến mất

  2. MySQL Thêm Ràng buộc Duy nhất

  3. Tính toán phần trăm trong MySQL dựa trên tổng số

  4. Viết hoa chữ cái đầu tiên của mỗi từ, trong bảng hiện có

  5. Hàm mật khẩu MySQL