2010-10-22 56 views
12

Tôi đang đối mặt với một vấn đề rất phổ biến về "Chọn hàng đầu N cho mỗi nhóm trong một bảng".chọn N hàng đầu cho mỗi nhóm trong một bảng

Xem xét bảng có các cột id, name, hair_colour, score.

Tôi muốn kết quả như vậy, với mỗi màu tóc, hãy cho tôi 3 tên người ghi bàn hàng đầu.

Để giải quyết việc này tôi đã nhận một cách chính xác những gì tôi cần trên Rick Osborne's blogpost "sql-getting-top-n-rows-for-a-grouped-query"

Đó giải pháp không hoạt động như mong đợi khi điểm số của tôi đều bình đẳng.

Trong ví dụ trên, kết quả như sau.

id name hair score ranknum 
--------------------------------- 
12 Kit Blonde 10 1 
    9 Becca Blonde 9 2 
    8 Katie Blonde 8 3 
    3 Sarah Brunette 10 1  
    4 Deborah Brunette 9 2 - ------- - - > if 
    1 Kim Brunette 8 3 

Xem xét hàng 4 Deborah Brunette 9 2. Nếu điều này cũng có cùng số điểm (10) giống như Sarah, thì ranknum sẽ là 2,2,3 cho kiểu tóc "Brunette".

Giải pháp cho điều này là gì?

+1

Bạn đang sử dụng RDBMS nào? –

+0

Có một giải pháp cho việc này tại http://stackoverflow.com/questions/3823939/ trong trường hợp bạn không sử dụng các Máy chủ SQL mới hơn. –

Trả lời

16

Nếu bạn đang sử dụng SQL Server 2005 hoặc mới hơn, bạn có thể sử dụng các hàm ranking và CTE để đạt được điều này:

;WITH HairColors AS 
(SELECT id, name, hair, score, 
     ROW_NUMBER() OVER(PARTITION BY hair ORDER BY score DESC) as 'RowNum' 
) 
SELECT id, name, hair, score 
FROM HairColors 
WHERE RowNum <= 3 

CTE này sẽ "phân vùng" dữ liệu của bạn bởi giá trị của cột hair , và mỗi phân vùng sau đó được sắp xếp theo điểm số (giảm dần) và nhận được một số hàng; số điểm cao nhất cho mỗi phân vùng là 1, sau đó là 2, v.v.

Vì vậy nếu bạn muốn TOP 3 của mỗi nhóm, chỉ chọn những hàng từ CTE có số RowNum trong 3 hoặc ít hơn (1, 2, 3) -> có bạn đi!

+0

ROW_NUMBER() OVER (PARTITION BY hair ORDER BY điểm DESC) là 'RowNum') khung trong dòng này không cân bằng. điều này có tương thích với ngữ pháp db2 sql không? – zinking

+0

@zinking: cảm ơn - đã có một lần đóng cửa quá nhiều .. đã sửa nó! Tôi không biết nếu DB2 hỗ trợ điều này (không biết đủ DB2) - nhưng nó chắc chắn là một chuẩn xây dựng tiêu chuẩn SQL ANSI/ISO - không phải là tính năng được phát minh của Microsoft :-) –

+1

Chết tiệt, điều này vừa mới tạo ra ngày của tôi! Giới thiệu về CTEs! –

0

Cách thuật toán đi lên với thứ hạng, là đếm số hàng trong sản phẩm chéo với số điểm bằng hoặc lớn hơn so với cô gái được đề cập, để tạo thứ hạng. Do đó trong trường hợp vấn đề bạn đang nói đến, lưới của Sarah sẽ trông giống như

a.name | a.score | b.name | b.score 
-------+---------+---------+-------- 
Sarah | 9  | Sarah | 9 
Sarah | 9  | Deborah | 9 

và tương tự cho Deborah, đó là lý do tại sao cả hai cô gái có xếp hạng 2 ở đây.

Vấn đề là khi có cà vạt, tất cả các cô gái đều có giá trị thấp nhất trong phạm vi được buộc do số này, khi bạn muốn họ có giá trị cao nhất thay thế. Tôi nghĩ rằng một thay đổi đơn giản có thể khắc phục điều này:

Thay vì so sánh lớn hơn hoặc bằng nhau, hãy sử dụng so sánh nghiêm ngặt hơn để đếm số lượng bé gái tốt hơn. Sau đó, thêm một vào đó và bạn có thứ hạng của bạn (mà sẽ đối phó với các mối quan hệ thích hợp). Vì vậy, lựa chọn bên trong sẽ là:

SELECT a.id, COUNT(*) + 1 AS ranknum 
FROM girl AS a 
    INNER JOIN girl AS b ON (a.hair = b.hair) AND (a.score < b.score) 
GROUP BY a.id 
HAVING COUNT(*) <= 3 

Có ai nhìn thấy bất kỳ vấn đề nào với cách tiếp cận này đã thoát khỏi thông báo của tôi không?

+0

Điều đó không chạy trong thời gian bậc hai? – b0fh

0

Sử dụng hợp chất này chọn mà xử lý vấn đề OP đúng

SELECT g.* FROM girls as g 
WHERE g.score > IFNULL((SELECT g2.score FROM girls as g2 
       WHERE g.hair=g2.hair ORDER BY g2.score DESC LIMIT 3,1), 0) 

Lưu ý rằng bạn cần phải sử dụng IFNULL đây để xử lý trường hợp khi bảng cô gái còn ít hàng cho một số loại tóc sau đó chúng tôi muốn xem trong câu trả lời sql (trong trường hợp OP nó là 3 bài).

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