【问题标题】:Select top N rows from each group从每个组中选择前 N 行
【发布时间】:2022-03-15 20:55:42
【问题描述】:

我的博客平台使用 mongodb,用户可以在其中创建自己的博客。所有博客的所有条目都在条目集合中。条目的文档如下所示:

{
  'blog_id':xxx,
  'timestamp':xxx,
  'title':xxx,
  'content':xxx
}

正如问题所说,有没有办法为每个博客选择最后 3 个条目?

【问题讨论】:

    标签: mongodb aggregation-framework grouping limit


    【解决方案1】:

    您需要首先按blog_idtimestamp 字段对集合中的文档进行排序,然后执行一个初始组,以降序创建原始文档的数组。之后,您可以使用文档对数组进行切片以返回前 3 个元素。

    在这个例子中可以遵循直觉:

    db.entries.aggregate([
        { '$sort': { 'blog_id': 1, 'timestamp': -1 } }, 
        {       
            '$group': {
                '_id': '$blog_id',
                'docs': { '$push': '$$ROOT' },
            }
        },
        {
            '$project': {
                'top_three': { 
                    '$slice': ['$docs', 3]
                }
            }
        }
    ])
    

    【讨论】:

    • 由于聚合可用,现在这是更好的答案。
    • 如果每个组有几千个文档,我猜组阶段会将它们全部保存在docs数组中,而我们只需要最后3个,不必保留任何东西别的。你知道是否有办法在 Mongo 4.2 中提高效率(在 docs 中最多保留 3 个文档)? (我猜在 4.4 中你可以使用自定义累加器函数。)
    【解决方案2】:

    如果你能忍受两件事,那么在基本 mongo 中做到这一点的唯一方法:

    • 条目文档中的一个附加字段,我们称之为“年龄”
    • 一个新的博客条目进行了额外的更新

    如果是这样,你可以这样做:

    1. 在创建新的介绍后,进行正常插入,然后执行此更新以增加所有帖子(包括您刚刚为该博客插入的帖子)的年龄:

      db.entries.update({blog_id: BLOG_ID}, {age:{$inc:1}}, false, true)

    2. 查询时,使用以下查询将返回每个博客最近的 3 个条目:

      db.entries.find({age:{$lte:3}, 时间戳:{$gte:STARTOFMONTH, $lt:ENDOFMONTH}}).sort({blog_id:1, age:1})

    请注意,此解决方案实际上是并发安全的(没有重复年龄的条目)。

    【讨论】:

    • 明白了。我没有想到这样的事情。创建新帖子时的额外更新不会有问题。但是,当用户删除帖子时,我们必须更新所有其他帖子的“年龄”字段。该更新只能在已删除帖子的“年龄”
    • 是的,您不应该将该更新限制为年龄 deleted_post.age。祝你好运。
    • 它适用于很少更新的少量记录,但是当我需要从 2 个用户之间的每次对话中获取 1 条最后一条消息时,将它与消息传递系统一起使用是否有效,当我有数千条消息时每分钟都有很多新消息的消息?我认为每次对数千条消息进行和更新“年龄”是无效的。你能为那个案例提供一些建议吗?
    • @oyatek 取决于您的确切用例和读/写比率。如果您针对您的具体问题提出问题,我会看看。
    • 是的,问题就在这里 - stackoverflow.com/questions/9859713/… - (我已经将其标记为已回答,感谢您的回答)
    【解决方案3】:

    Mongo 5.2 开始,这是新的$topN 聚合累加器的完美用例:

    // { blog_id: "a", title: "plop",  content: "smthg" }
    // { blog_id: "b", title: "hum",   content: "meh"   }
    // { blog_id: "a", title: "hello", content: "world" }
    // { blog_id: "a", title: "what",  content: "ever"  }
    db.collection.aggregate([
      { $group: {
        _id: "$blog_id",
        messages: { $topN: { n: 2, sortBy: { _id: -1 }, output: "$$ROOT" } }
      }}
    ])
    // {
    //   _id: "a",
    //   messages: [
    //     { blog_id: "a", title: "what",  content: "ever" },
    //     { blog_id: "a", title: "hello", content: "world" }
    //   ]
    // }
    // {
    //   _id: "b",
    //   messages: [
    //     { blog_id: "b", title: "hum", content: "meh" }
    //   ]
    // }
    

    这应用了$topN 组累积:

    • 为每个组获取前 2 个 (n: 2) 元素
    • 前 2 位,由 sortBy: { _id: -1 } 定义,在这种情况下表示插入顺序相反
    • 并且对于每条记录,将整个记录推送到组列表 (output: "$$ROOT") 中,因为 $$ROOT 代表正在处理的整个文档。

    【讨论】:

      【解决方案4】:

      可以使用组(聚合),但这会创建全表扫描。

      您真的需要正好 3 个,还是可以设置一个限制...例如:上周/月最多 3 个帖子?

      【讨论】:

      • 理想情况下,我想准确选择 3 个,但如果除了数据非规范化之外我找不到解决方案,上个月的最多 3 个帖子就足够了。你能给我一个如何做到这一点的例子吗?从我读过的所有 mongodb 的 map reduce 教程中,它们只展示了如何计算统计数据(聚合)......
      【解决方案5】:

      this answer using map reduce by drcosta from another question 成功了

      In mongo, how do I use map reduce to get a group by ordered by most recent

      mapper = function () {
        emit(this.category, {top:[this.score]});
      }
      
      reducer = function (key, values) {
        var scores = [];
        values.forEach(
          function (obj) {
            obj.top.forEach(
              function (score) {
                scores[scores.length] = score;
            });
        });
        scores.sort();
        scores.reverse();
        return {top:scores.slice(0, 3)};
      }
      
      function find_top_scores(categories) {
        var query = [];
        db.top_foos.find({_id:{$in:categories}}).forEach(
          function (topscores) {
            query[query.length] = {
              category:topscores._id,
              score:{$in:topscores.value.top}
            };
        });
        return db.foo.find({$or:query});
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-10
        • 2018-10-29
        • 2015-03-23
        • 2017-12-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多