原答案
从技术上讲,使用单个 SQL 查询无法实现您想要完成的任务。如果您只希望每个学科有一个学生,您可以使用 GROUP BY 实现这一目标,但在您的情况下它不会起作用。
我能想到的让每个科目有 5 名学生的唯一方法是编写 x 查询,每个科目一个查询,然后使用 UNION 将它们粘合在一起。这样的查询最多会返回 5x 行。
由于您想根据分数获得前 5 名学生,因此您必须使用 ORDER BY 子句,它与 UNION 子句结合使用会导致错误。为避免这种情况,您将不得不使用子查询,以便 UNION 和 ORDER BY 子句不在同一级别。
查询:
-- Select the 5 students with the highest mark in the `PCM` subject.
(
SELECT *
FROM student
WHERE subject = 'PCM'
ORDER BY studentMarks DESC
LIMIT 5
)
UNION
(
SELECT *
FROM student
WHERE subject = 'PCB'
ORDER BY studentMarks DESC
LIMIT 5
)
UNION
(
SELECT *
FROM student
WHERE subject = 'ART'
ORDER BY studentMarks DESC
LIMIT 5
);
查看this SQLFiddle 自行评估结果。
更新答案
本次更新旨在让超过 5 名学生在许多学生在特定科目中成绩相同的情况下获得。
我们不使用LIMIT 5 来获得前5 行,而是使用LIMIT 4,1 来获得第五高的成绩,并使用它来获得在给定科目中成绩高于或等于该成绩的所有学生。但是,如果一个科目中有 LIMIT 4,1 将返回 NULL。在这种情况下,我们基本上想要每个学生,所以我们使用最低成绩。
要实现上述目的,您需要使用以下代码 x 次,与您拥有的主题一样多,并使用 UNION 将它们连接在一起。很容易理解,这个解决方案可以用于少数不同的主题,否则查询的范围将变得无法维护。
代码:
-- Select the students with the top 5 highest marks in the `x` subject.
SELECT *
FROM student
WHERE studentMarks >= (
-- If there are less than 5 students in the subject return them all.
IFNULL (
(
-- Get the fifth highest grade.
SELECT studentMarks
FROM student
WHERE subject = 'x'
ORDER BY studentMarks DESC
LIMIT 4,1
), (
-- Get the lowest grade.
SELECT MIN(studentMarks)
FROM student
WHERE subject = 'x'
)
)
) AND subject = 'x';
查看this SQLFiddle 自行评估结果。
替代方案:
经过一些研究,我发现了一种替代的、更简单的查询,它可以根据您提供的数据产生与上述结果相同的结果,而无需“硬编码”每个主题查询。
在以下解决方案中,我们定义了几个变量来帮助我们控制数据:
- 一个缓存上一行的主题和
- 用于保存增量值,以区分具有相同主题的行。
查询:
-- Select the students having the top 5 marks in each subject.
SELECT studentID, studentName, studentMarks, subject FROM
(
-- Use an incremented value to differentiate rows with the same subject.
SELECT *, (@n := if(@s = subject, @n +1, 1)) as n, @s:= subject
FROM student
CROSS JOIN (SELECT @n := 0, @s:= NULL) AS b
) AS a
WHERE n <= 5
ORDER BY subject, studentMarks DESC;
查看this SQLFiddle 自行评估结果。
想法来自以下线程: