如何在MySQL中找到每个事件的最佳分数?

时间:2012-12-28 11:37:05

标签: mysql aggregate-functions

我有一个MySQL表,其中包含运动员进行的一系列测试的数据。我想为每个活动获得最好的结果。

这是包含运动员所有测试数据的表格:

+---------+-----------+-------+
| eventId | athleteId | score |
+---------+-----------+-------+
| 1       | 129907    | 900   |
| 2       | 129907    | 940   |
| 3       | 129907    | 927   |
| 4       | 129907    | 856   |
| 1       | 328992    | 780   |
| 2       | 328992    | 890   |
| 3       | 328992    | 936   |
| 4       | 328992    | 864   |
| 1       | 492561    | 899   |
| 2       | 492561    | 960   |
| 3       | 492561    | 840   |
| 4       | 492561    | 920   |
| 5       | 487422    | 900   |
| 6       | 487422    | 940   |
| 7       | 487422    | 927   |
| 5       | 629876    | 780   |
| 6       | 629876    | 890   |
| 7       | 629876    | 940   |
| 5       | 138688    | 899   |
| 6       | 138688    | 950   |
| 7       | 138688    | 840   |
+---------+-----------+-------+

我需要选择最好的标准阵容,进行最佳测试。我要找的结果应该是:

+---------+-----------+-------+
| eventId | athleteId | score |
+---------+-----------+-------+
| 1       | 129907    | 900   |
| 2       | 492561    | 960   |
| 3       | 328992    | 936   |
| 4       | 492561    | 920   |
| 5       | 487422    | 900   |
| 6       | 138688    | 950   |
| 7       | 629876    | 940   |
+---------+-----------+-------+

2 个答案:

答案 0 :(得分:5)

如果你想要可靠地获得胜利者(和联合获胜者)。以下SQL语句应该这样做......

SELECT athleteId, a.eventId, a.score
FROM tests AS a
JOIN (
  -- This select finds the top score for each event
  SELECT eventId, MAX(score) AS score
  FROM tests 
  GROUP BY eventId
) AS b
-- Join on the top scores
ON a.eventId = b.eventId
AND a.score = b.score

我执行子选择以获得每个事件的最高分数,然后执行内部联接以获得在事件中获得最高分数的各个记录。


其他信息

我已在评论中的会话中汇编了以下信息。

为什么解决方案的基本组不可靠?

SELECT athleteId, eventId, score
FROM (
  SELECT athleteId, eventId, score
  FROM tests
  ORDER BY eventId, score DESC
) AS a
GROUP BY eventId

我们正在根据事件和分数订购的记录集创建一个组。然后,我们使用分组从列中选择值,以便为每个事件选择一条记录。

首先要注意的是

如果您使用GROUP BY条款,则不再谈论个别记录,而是无序记录集

您可以使用聚合函数在MySQL http://dev.mysql.com/doc/refman/5.1/en/group-by-functions.html中执行一些非常强大且有用的交叉记录计算,但为了将这些组关联回个别记录,您可能需要执行JOIN

在第二个例子中,我们将组返回为个人记录。

为什么第二个例子似乎有效?

而不是在SQL语言中非聚合列是非法的,在MySQL中它们已被允许,虽然我不能说明原因,但可能出于性能原因而在非规范化列中或出于某种原因您确定组中列的值不会更改。

MySQL选择要为组中的非聚合列返回的最简单值。它恰好在记录集分组之前选择了它遇到的第一个值,但是,它不一定会一直这样做!

MySQL文档指出包含GROUP BY的选择中非聚合列的值是不确定的。这意味着不应假设非聚合列的结果值是分组之前的事件(即记录集中的任何排序)的结果,尽管实际上在当前实现中它看起来是这样的。

在未来的版本中可能不是这种情况,如果你运行两次,结果甚至可能不一样。 明确记录的事实足以让我避免它!

为什么非聚合列不确定?

我推断他们打算留下algos的实现,以便为将来的优化打开分组,这可能会在分组之前忽略或破坏记录的原始排序。

从概念上讲,如果将一组记录想象为单个单元而不是单个记录的集合,则有意义。对于非聚合列,可以返回许多可能的值,并且在选择的那一点上没有隐含的条件来选择其中一个,您必须记住分组之前记录的方式。

风险

我使用这种方法的所有查询都可能在某些时候开始起作用。他们可能会返回未获得该事件最高分的记录的值。

此外,这个错误不会立即显现,因此跟踪最近MySQL升级的原因需要一段时间。我还可以保证我会忘记这个潜在的陷阱,当它确实发生时所有这些都是一个问题,所以我可能最终会陷入一个较旧的不太安全的MySQL版本,直到我有机会调试它正确等等...痛苦......

为什么加入解决方案有所不同?

JOIN语句中的子选择不使用非聚合列,聚合是确定的,因为它们与整个组相关,而不是单个记录。无论记录在分组之前的顺序如何,答案总是相同的。

我使用了JOIN语句将这些组与我们感兴趣的各个记录联系起来。在某些情况下,这可能意味着每个组都有多个单独的记录。例如,当涉及两名运动员具有相同最高得分的抽奖时,我将要么必须返回两个记录,要么任意选择一个。我相信我们会想要所有得分最高的人,所以我没有提供任何规则来选择两名可能参赛的运动员。


挑选一条记录作为获胜者

为了选择一条记录作为明显的赢家,我们需要一种能够将获胜者与选手分开的方法。我们可能会选择最终的胜利者作为第一个获得最高分的运动员,另一名运动员要领先,他们必须更好地获得之前的分数。

要做到这一点,我们必须有一种方法来确定测试的顺序,所以我们引入一个testId列,它将随着我们获得的每个新结果而递增。当我们有了这个时,我们就可以执行以下查询...

SELECT a.eventId, athleteId, a.score
FROM tests AS a
JOIN (
  -- This select finds the first testId for each score + event combination
  SELECT MIN(testId) AS testId, c.eventId, c.score
  FROM tests AS c
  JOIN (
    -- This select finds the top score for each event
    SELECT eventId, MAX(score) AS score
    FROM tests
    GROUP BY eventId
  ) AS d
  ON c.eventId = d.eventId
  AND c.score = d.score
  GROUP BY eventId, score
) AS b
ON a.testId = b.testId

这里发生的是我们创建表示每个事件的最高得分的组,然后我们用内部连接表示每个得分和事件组合的最低testId的组,最后用测试表中的记录进行内部连接以获得个人记录。

这也可以写成(执行计划稍有不同),如下所示。

SELECT a.eventId, athleteId, a.score
FROM tests AS a
JOIN (
  -- This select finds the top score for each event
  SELECT eventId, MAX(score) AS score
  FROM tests
  GROUP BY eventId
) AS b
ON a.eventId = b.eventId
AND a.score = b.score
JOIN (
  -- This select finds the first testId for each score + event combination
  SELECT MIN(testId) AS testId, eventId, score
  FROM tests
  GROUP BY eventId, score
) AS c
ON a.testId = c.testId

基于解决方案的基本组在较少的SQL中实现了相同的结果,但相比之下它的优化效果非常差。如果我们向表中添加索引,则解决方案的基本组不会使用索引,并且需要在tests表中的所有记录上使用两个filesorts(通过表进行额外的运行以使其按顺序排列)。但是,上面的原始嵌套子选择查询可以很好地优化。

答案 1 :(得分:0)

试试这个:

SELECT t1.eventId, t1.athleteId, t1.score  
FROM tests t1 
LEFT JOIN tests t2 ON t2.eventId = t1.eventId AND t2.score > t1.score 
WHERE t2.athleteId IS NULL
ORDER BY t1.eventId 

http://sqlfiddle.com/#!2/80e34/3/0