如果您想可靠地获得获胜者(和联合获胜者)。下面的 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 中实现了相同的结果,但相比之下它的优化非常差。如果我们向表中添加索引,则基本分组解决方案不使用索引,并且需要对测试表中的所有记录进行两个文件排序(通过表进行额外的运行以将其排序)。但是,上面的原始嵌套子选择查询优化得非常好。