不理解查询如何从每个组中检索前n个记录

时间:2014-01-09 16:49:20

标签: sql greatest-n-per-group

我遇到了一个问题,我试图从每个组(日)或数据库中的记录中获取最高'n'条记录。经过一堆挖掘后,我发现了一些很棒的答案,事实上他们确实解决了我的问题。

然而,我的正义使我无法正确理解为什么这些“计数”解决方案有效。如果具有更好SQL知识的人可以解释,那将非常棒。

编辑:这里有更多细节

假设我有一个下面描述的表格和这个样本数据。 (为了简单起见,我有一个专栏跟踪下一个即将到来的午夜的时间,以便更好地将“每天”分组。)

id | vote_time | time_of_midnight |    name    | votes_yay | votes_nay
------------------------------------------------------------------------
 1 |     a     |        b         |  Person p  |    24     |     36
 1 |     a     |        b         |  Person q  |    20     |     10
 1 |     a     |        b         |  Person r  |    42     |     22
 1 |     c     |        d         |  Person p  |     8     |     10
 1 |     c     |        d         |  Person s  |   120     |     63
  • 每天可能有数十或数百名“人”(b,d,...)
  • id是我需要分组的其他专栏(如果有帮助,你可以将其视为选举ID)

我正在尝试按降序计算每天投票数最多的前5名。我能够使用引用的文章创建一个可以给我的查询以下结果(在Oracle上):

SELECT name, time_of_midnight, votes_yay, votes_nay, (votes_yay+votes_nay) AS total_votes
FROM results a
WHERE id=1 AND (
    SELECT COUNT(*) 
    FROM results b
    WHERE b.id=a.id AND b.time_of_midnight=a.time_of_midnight AND (a.votes_yay+a.votes_nay) >= (b.votes_yay+b.votes_nay)) <= 5
ORDER BY time_of_midnight DESC, total_votes DESC;


   name    | time_of_midnight | votes_yay | votes_nay | total_votes
------------------------------------------------------------------------
 Person s  |         d        |     120   |     63    |     183
 Person p  |         d        |       8   |     10    |      18
 Person r  |         b        |      42   |     22    |      64
 Person p  |         b        |      24   |     36    |      60
 Person q  |         b        |      20   |     10    |      30

所以我不太确定

  • 为什么这种计数方法有效?
  • [愚蠢]:为什么我不需要在内部查询中包含name以确保它不会错误地加入数据?

1 个答案:

答案 0 :(得分:1)

让我们从您的查询实际计算具有最低投票数的前5个名称开始。要获得最高编号的前5名,您需要更改此条件:

(a.votes_yay+a.votes_nay) >= (b.votes_yay+b.votes_nay)

进入这个:

(a.votes_yay+a.votes_nay) <= (b.votes_yay+b.votes_nay)

或者,也许,这(相同):

(b.votes_yay+b.votes_nay) >= (a.votes_yay+a.votes_nay)

(后一种形式在我看来更可取,但仅仅因为它与其他两个比较一致,左侧有b列,而a列上有results列。右侧。这与逻辑的正确性完全无关。)

从逻辑上讲,发生了什么事。对于id中的每一行,服务器将在同一个表中查找与给定行的time_of_midnight(id, time_of_midnight)匹配且具有相同或更高总票数超过给定行数。然后,它将对找到的行进行计数,并检查结果是否不大于5,即如果同一count <= 5组中的不超过5行的投票数与给定行中的相同或更高。

例如,如果给定的行恰好是其组中投票最多的行,则子查询将只找到相同的行(假设没有关系),因此计数将为1.即小于5 - 因此,给定的行将有资格获得输出。

如果给定的行将是组中投票次数第二的项,则子查询将找到相同的行顶部投票的项目(再次,假设没有关联),这将给出count。再次,匹配SELECT name, time_of_midnight, votes_yay, votes_nay, (votes_yay+votes_nay) AS total_votes FROM results a WHERE id=1 AND ( SELECT COUNT(*) + 1 FROM results b WHERE b.id=a.id AND b.time_of_midnight=a.time_of_midnight AND (b.votes_yay+b.votes_nay) > (a.votes_yay+a.votes_nay)) <= 5 ORDER BY time_of_midnight DESC, total_votes DESC; 条件,因此该行将在输出中返回。

通常,如果某行根据总投票数在其组中排名为# N ,则表示该组中有 N 行投票号与给定行中的号码相同或更高(我们仍假设没有联系)。因此,当您以这种方式计算投票时,您实际上是在计算给定行的排名。

现在,如果存在关系,则使用此方法可能会减少每组的结果。实际上,如果一个组在最大行数上绑定了6行或更多行,那么输出中的该组将得到 no rows ,因为子查询永远不会返回小于6的计数值

这是因为实际上所有排名靠前的项目都会被排名为6(或者不管它们的数量是多少)而不是1.要将它们排名为1,您可以尝试对同一查询进行以下修改:< / p>

{{1}}

现在,子查询将仅查找 ,以获得投票数高于给定行的行。结果计数将增加1,这将是给定行的排名(以及与5进行比较的值)。

所以,如果计数是例如10,10,8,7等等,排名将计算为1,1,3,4等,而不是原始版本的2,2,3,4等。

当然,这意味着输出现在可能更多,而不是每组5行。例如,如果投票分配为10,9,8,8,8,8,6等,你将获得10,9和所有8(因为排名将是1,2,3,3,3, 3,7 ......)。要为每个组返回完全 5个名称(假设其中至少有5个),您可能需要完全考虑不同的方法。