1

我有一个 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   |
+---------+-----------+-------+
4

2 回答 2

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

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

为什么非聚合列是不确定的?

我会推断他们打算让用于分组的算法的实现开放以供将来优化,这可能会在分组之前忽略或破坏记录的原始顺序。

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

风险

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

此外,此错误不会立即显现,因此跟踪最近升级 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

基本的 group by 解决方案在较少的 SQL 中实现了相同的结果,但相比之下它的优化非常差。如果我们向表中添加索引,则基本分组解决方案不使用索引,并且需要对测试表中的所有记录进行两个文件排序(通过表进行额外的运行以将其排序)。但是,上面的原始嵌套子选择查询优化得非常好。

于 2012-12-28T12:02:07.427 回答
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

于 2012-12-28T11:56:05.607 回答