为表中的每个组选择前N行

时间:2010-10-22 15:45:44

标签: sql ranking

我面临一个非常常见的问题,即“为表格中的每个组选择前N行”。

考虑一个包含id, name, hair_colour, score列的表格。

我想要一个结果集,这样,对于每种头发颜色,让我获得前三名得分手的名字。

为了解决这个问题,我完全得到了Rick Osborne's blogpost "sql-getting-top-n-rows-for-a-grouped-query"

所需的内容

当我的分数相等时,该解决方案无法正常工作。

在上面的例子中,结果如下。

 id  name  hair  score  ranknum
---------------------------------
 12  Kit    Blonde  10  1
  9  Becca  Blonde  9  2
  8  Katie  Blonde  8  3
  3  Sarah  Brunette 10  1    
  4  Deborah Brunette 9  2 - ------- - - > if
  1  Kim  Brunette 8  3

考虑行4 Deborah Brunette 9 2。如果这个得分(10)与Sarah相同,那么“褐发女郎”型头发的排名将为2,2,3。

这是什么解决方案?

3 个答案:

答案 0 :(得分:17)

如果您使用的是SQL Server 2005或更高版本,则可以使用排名功能和CTE来实现此目的:

;WITH HairColors AS
(SELECT id, name, hair, score, 
        ROW_NUMBER() OVER(PARTITION BY hair ORDER BY score DESC) as 'RowNum'
)
SELECT id, name, hair, score
FROM HairColors
WHERE RowNum <= 3

此CTE将按hair列的值“分区”您的数据,然后每个分区按分数(降序)排序并获取行号;每个分区的最高分为1,然后是2等。

因此,如果您想要每组的TOP 3,请仅选择CTE中RowNum为3或更少(1,2,3)的那些行 - &gt;你去吧!

答案 1 :(得分:0)

算法提出等级的方式是计算交叉积中的行数,其得分等于或大于所讨论的女孩,以便生成等级。因此,在您谈论的问题案例中,Sarah的网格看起来像

a.name | a.score | b.name  | b.score
-------+---------+---------+--------
Sarah  | 9       | Sarah   | 9
Sarah  | 9       | Deborah | 9
对于Deborah来说也是如此,这就是为什么这两个女孩在这里的等级为2的原因。

问题在于,当有一个平局时,所有女孩都会因为这个计数而在绑定范围内取最低值,而你希望他们取代最高值。我认为一个简单的改变可以解决这个问题:

使用严格的大于比较来计算严格更好的女孩数量,而不是大于或等于比较。然后,添加一个,你有你的等级(将适当处理关系)。因此内部选择将是:

SELECT a.id, COUNT(*) + 1 AS ranknum
FROM girl AS a
  INNER JOIN girl AS b ON (a.hair = b.hair) AND (a.score < b.score)
GROUP BY a.id
HAVING COUNT(*) <= 3

任何人都可以看到这种方法的任何问题都没有引起我的注意吗?

答案 2 :(得分:0)

使用此复合选择正确处理OP问题

SELECT g.* FROM girls as g
WHERE g.score > IFNULL( (SELECT g2.score FROM girls as g2
                WHERE g.hair=g2.hair ORDER BY g2.score DESC LIMIT 3,1), 0)

注意你需要在这里使用IFNULL处理表女孩对于某些类型的的行数较少的情况然后我们想在sql中看到答案(在OP中)案例是3项)。