Để trích xuất ba học sinh có điểm cao nhất trong từng môn học từ bảng điểm, MySQL cung cấp nhiều cách tiếp cận — từ phương pháp truyền thống dùng subquery đến kỹ thuật hiện đại dựa trên hàm cửa sổ. Dưới đây là giải pháp tối ưu, rõ ràng và tương thích với MySQL 8.0 trở lên.
Cấu trúc dữ liệu mẫu
Giả sử ta có bảng scores với các cột:
student_id: mã số học sinh (INT)subject: tên môn học (VARCHAR)score: điểm số (DECIMAL(5,2))
Truy vấn chính xác bằng hàm cửa sổ
Sử dụng ROW_NUMBER() để đánh số thứ hạng riêng biệt cho từng môn học theo thứ tự điểm giảm dần:
SELECT
student_id,
subject,
score
FROM (
SELECT
student_id,
subject,
score,
ROW_NUMBER() OVER (
PARTITION BY subject
ORDER BY score DESC, student_id ASC
) AS position
FROM scores
) AS ranked
WHERE position <= 3;
Chú thích quan trọng:
PARTITION BY subjectchia dữ liệu thành các nhóm theo môn học.ORDER BY score DESC, student_id ASCđảm bảo thứ hạng ưu tiên điểm cao trước; nếu có điểm trùng nhau thì ưu tiên học sinh cóstudent_idnhỏ hơn để đảm bảo tính ổn định và dễ kiểm soát.- Bảng con
rankedchứa toàn bộ bản ghi kèm vị trí xếp hạng — lọc ngoài cùng chỉ giữ lại các bản ghi cóposition ≤ 3.
Phương án thay thế cho phiên bản MySQL cũ hơn (< 8.0)
Nếu hệ thống đang chạy MySQL 5.7 hoặc thấp hơn, không hỗ trợ hàm cửa sổ, có thể dùng subquery lồng ghép với đếm tự động:
SELECT s1.student_id, s1.subject, s1.score
FROM scores s1
WHERE (
SELECT COUNT(*)
FROM scores s2
WHERE s2.subject = s1.subject
AND s2.score > s1.score
) < 3
ORDER BY s1.subject, s1.score DESC;
Cách này so sánh từng bản ghi s1 với tất cả bản ghi cùng môn học s2, đếm số điểm cao hơn — nếu ít hơn 3 điểm cao hơn, tức là s1 nằm trong top 3.
Lưu ý về hiệu năng
Đối với bảng lớn, hãy đảm bảo có chỉ mục phù hợp:
CREATE INDEX idx_subject_score ON scores(subject, score DESC);
Chỉ mục ghép này giúp tăng tốc đáng kể cả hai phương án trên khi thực hiện phân vùng theo môn và sắp xếp theo điểm.