查询单个表中每个组中的前 5 名候选人

Posted

技术标签:

【中文标题】查询单个表中每个组中的前 5 名候选人【英文标题】:Query for getting top 5 candidate in every group in single table 【发布时间】:2018-01-11 10:01:51 【问题描述】:

我有一个表格,其中列出了每个科目的学生分数,我必须以这样一种方式进行查询,以便我能够获得每个科目中所有获得最高分的前 5 名学生。

这是一个示例表:

我的预期输出看起来像:

PCM、ART、PCB的前五名学生,根据学生成绩,如果有两个或更多学生安全与这些记录相同,也需要在单个查询列表中。

【问题讨论】:

请提供表结构 请参阅如何创建最小、完整和可验证的示例***.com/help/mcve @Saurabh Mistry 希望这能消除您的疑虑。 每门学科的前 5 名学生在最高分的基础上得到保障 Get top n records for each group of grouped results的可能重复 【参考方案1】:

原答案

从技术上讲,使用单个 SQL 查询无法实现您想要完成的任务。如果您只希望每个学科有一个学生,您可以使用 GROUP BY 实现这一目标,但在您的情况下它不会起作用。

我能想到的让每个科目有 5 名学生的唯一方法是编写 x 查询,每个科目一个查询,然后使用 UNION 将它们粘合在一起。这样的查询最多会返回 5x 行。

由于您想根据分数获得前 5 名学生,因此您必须使用 ORDER BY 子句,它与 UNION 子句结合使用会导致错误。为避免这种情况,您将不得不使用子查询,以便 UNIONORDER 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 自行评估结果。


想法来自以下线程:

Get top n records for each group of grouped results

How to SELECT the newest four items per category?

Select X items from every type

Getting the latest n records for each group

【讨论】:

通过这个查询,每组只返回五行,如果两个或多个学生获得相同的分数怎么办。所以限制不是个好主意。 你看到了吗?当我说你没有很好地解释你想要什么时,这就是我的意思。您的问题最初并未提出这样的要求。我已经更新了我的答案以涵盖这种情况@Kandy?告诉我它是否如你所愿。 感谢您的耐心等待。下次我肯定会尝试以更好的方式和更好的格式来解释我的问题。再次感谢。 它是否如你所愿@Kandy?如果是这样,请善待它,为这项工作投赞成票。根据您提供的数据,它按预期运行。 不客气@Kandy,你说得对,很难维持如此规模的查询,尤其是在处理大量不同主题时。再看看我的答案,因为我更新了它以包含一个更小但等效的查询,可以轻松维护。我还提供了一些指向类似问题的链接,以便您也可以从那里的答案中受益。【参考方案2】:

下面的查询几乎产生了我想要的结果,希望这个查询将来可以帮助其他人。

SELECT a.studentId, a.studentName, a.StudentMarks,a.subject  FROM testquery AS a WHERE 
(SELECT COUNT(*) FROM testquery AS b 
WHERE b.subject = a.subject AND b.StudentMarks >= a.StudentMarks) <= 2 
ORDER BY a.subject ASC, a.StudentMarks DESC

【讨论】:

以上是关于查询单个表中每个组中的前 5 名候选人的主要内容,如果未能解决你的问题,请参考以下文章

SQL - 需要在单个表中标识多个组中的最新条目

猪中按查询的内部组中的前 3 条记录

数据库设计

选择“高”列中的前 5 个最大记录,并从同一查询中的“低”列和按股票名称分区的同一个表中选择 5 个最小记录

sql查询语句:top n的用法

跳过每组中的前 n 行