【问题标题】:Get top 50 records for a certain value w/ mongo and meteor使用 mongo 和流星获取特定值的前 50 条记录
【发布时间】:2014-12-29 02:05:27
【问题描述】:

在我的流星项目中,我有一个排行榜,它在图表上显示每个级别的玩家,分布在游戏中的每个级别。为简单起见,假设有 1-100 级。目前,为了避免流星过载,我只是告诉服务器将每条新的记录发送给我两周前,但这对于准确的排行榜来说是不够的。

我要做的是显示代表每个级别的 50 条记录。因此,如果第 1 级有 100 条记录,第 2 级有 85 条记录,第 3 级有 65 条记录,第 4 级有 45 条记录,我想显示每个级别的 最新 50 条记录,所以我将分别有 [50, 50, 50, 45] 条记录。

数据看起来像这样:

{
    snapshotDate: new Date(),
    level: 1,
    hp: 5000,
    str: 100
}

我认为这需要一些 mongodb 聚合,但我无法完全弄清楚如何在一个查询中执行此操作。但是,将其分成两部分是微不足道的 - 选择所有记录,按级别分组,按日期对每个级别进行排序,然后从每个级别中获取最后 50 条记录。但是,如果可以的话,我宁愿在一次手术中完成。目前可以做这样的事情吗?

【问题讨论】:

  • 您能在收藏中发布一个示例文档吗?清楚地描述文档结构后,提供解决方案会变得容易得多。
  • @BatScream 我已在问题中添加了文档的小版本。
  • Collection.find({level:1},{limit:50});
  • @Sindis 我想找到每个级别的最新 50 条,而不是一个级别,而不仅仅是 50 条任意记录。
  • 你将不得不循环它然后通过每个级别(使用 if 子句检查是否有任何记录)并根据需要限制它(限制记录,如果你有 45 它将打印你 45 , 而不是 50)

标签: mongodb meteor mongodb-query


【解决方案1】:

目前无法在聚合管道中获取组的n 顶部记录。对此有一个未解决的未解决问题:https://jira.mongodb.org/browse/SERVER-9377

有两种解决方案:

  • 保持您现在的文档结构并进行汇总,但是, 在客户端获取 n 顶部记录并切掉每个组的剩余记录。

代码:

var top_records = [];

db.collection.aggregate([
// The sort operation needs to come before the $group,
// because once the records are grouped by level, 
// there exists only one document per group.
{$sort:{"snapshotDate":-1}},
// Maintain all the records in an array in sorted order. 
{$group:{"_id":"$level","recs":{$push:"$$ROOT"}}},
],{allowDiskUse: true}).forEach(function(level){
     level.recs.splice(50);   //Keep only the top 50 records.
     top_records.push(level);
})

请记住,这会加载每个 level 的所有文档并删除客户端中不需要的记录。

  • 改变您的文档结构以完成您真正需要的工作。如果 你只需要顶部的 n 记录,始终保持排序 根文档中的顺序。这是使用sorted capped array 完成的。

您的文档将如下所示:

{
 level:1,
 records:[{snapshotDate:2,hp:5000,str:100},
          {snapshotDate:1,hp:5001,str:101}]
}

其中,records 是一个大小为 n 的封顶数组,并且始终包含按其snapshotDate 降序排列的子文档。

为了使records 数组以这种方式工作,当我们需要为任何level 插入文档时,我们总是执行更新操作。

db.collection.update({"level":1},
            {$push:{
                    recs:{
                          $each:[{snapshotDate:1,hp:5000,str:100},
                                 {snapshotDate:2,hp:5001,str:101}],
                          $sort:{"snapshotDate":-1},
                          $slice:50 //Always trim the array size to 50.
                         }
            }},{upsert:true})

这样做的目的是始终将records 数组的大小保持为50,并且每当在level 处插入新的子文档时始终对记录进行排序。

一个简单的查找,db.collection.find({"level":{$in:[1,2,..]}}),将按顺序为您提供前 50 条记录,每个选定的level

【讨论】:

  • 这是一个有趣的解决方案。本质上,我会在输入的过程中对数据进行“预排序”(即,“在 x 级插入此记录,然后根据需要进行排序/切片”)?如果我已经有一堆记录(大约 20k 分布在大约 100 个级别),有没有一种简单的方法可以将它们拉入?此外,如果这不是非常紧急,我可以等到$slice 的问题得到解决吗?如果您不介意,在这种情况下会是什么样子?
  • 是的。你是对的。更新操作符:docs.mongodb.org/manual/reference/operator/update/slice,是在 2.4 中为此目的而引入的。如果您已经有很多记录,您可以使用 map-reduce 将它们迁移到新的文档结构中。这将简单直接,但您需要对现有应用程序代码进行大量更改,重构查询。话虽如此,如果您刚刚开始编写应用程序代码,则可以采用这种方法。 JIRA 已打勾现在已经开放了大约两年半。
  • 我对它很快会被修复持怀疑态度。如果您选择保留当前文档结构,则需要在客户端执行切片,或者为每个级别触发多个查询,请记住 - 您肯定必须为每个级别触发至少一个查询。这让情况变得更糟。
  • 当然,考虑到日期,这与预期的一样——我最初没有注意到问题发布日期。好吧,在我需要为这个排行榜擦除我的游戏数据之前,我还有一段时间需要等待,所以到时候我会重新评估。现在,我会接受这个答案,因为它经过深思熟虑并且是正确的,尽管需要对结构进行更改。谢谢。
  • 不客气,如果发现不同的方法,请更新。
猜你喜欢
  • 2017-11-04
  • 1970-01-01
  • 2020-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-13
  • 2019-07-01
  • 1970-01-01
相关资源
最近更新 更多