首页 > 解决方案 > MongoDb聚合 - 滑动窗口平均值?

问题描述

我正在尝试使用 mongodb 聚合框架来平均每学期每分钟最后一小时的“分数”。我当然没有按分钟分组的问题..只是不清楚按分钟分组的最佳方式,但然后让 mongo 每分钟返回一个小时来计算平均分数。在这里搜索并找到一些选项后,我觉得 $bucket 由于性能原因并不是最好的方法,map/reduce 也是如此。所以只是在寻找意见。我不需要经常运行此查询,因为我通常每分钟都会在我的应用程序中执行此运行计算.. 但需要首先根据我收集的原始数据“播种”数据,

样本原始文件:

{
    "_id" : ObjectId("603e472aba52820ab3e139ae"),
    "term" : "sports",
    "created_at" : ISODate("2021-03-02T14:09:46.701Z"),
    "score" : -0.331166666666667,
},
{
    "_id" : ObjectId("6031272aba52820ab3e139ef"),
    "term" : "sports",
    "created_at" : ISODate("2021-03-02T14:09:55.000Z"),
    "score" : 1.05
},
{
    "_id" : ObjectId("io12iopeipweipqeqweqwewq"),
    "term" : "food",
    "created_at" : ISODate("2021-03-10T16:02:12.091Z"),
    "score" : 0.25,
}

我想要一个类似于的结果集

term   | for minute "x"      | average of scores from (x - 1 hour) to x
------------------------------------------------------------------------------------
sports | 03/31/2021 14:10:00 | (average of all "sports" scores from 3/31/2021 13:10:00 to 3/31/2021 14:10:00)
food   | 03/31/2021 14:10:00 | (average of all "food" scores from 3/31/2021 13:10:00 to 3/31/2021 14:10:00)
sports | 03/31/2021 14:11:00 | (average of all "sports" scores from 3/31/2021 13:11:00 to 3/31/2021 14:11:00)

预先感谢您提供任何可能愿意提供的建议!

标签: mongodbaggregation-framework

解决方案


试试这个:

db.collection.aggregate([
   {
      $group: {
         _id: "$term",
         min_created: { $min: "$created_at" },
         data: { $push: "$$ROOT" }
      }
   },
   {
      $project: {
         score: {
            $map: {
               input: { // generate sequence of 60 Minutes
                  $map: {
                     input: { $range: [0, 60] },
                     in: { $add: ["$min_created", { $multiply: ["$$this", 60, 1000] }] }
                  }
               },
               as: "ts",
               in: {
                  range_start: "$$ts",
                  range_end: { $add: ["$$ts", 1000 * 60 * 60] },
                  scores: {
                     $filter: { // select data fitting in range
                        input: "$data",
                        cond: {
                           $and: [
                              { $gte: ["$$this.created_at", "$$ts"] },
                              { $lt: ["$$this.created_at", { $add: ["$$ts", 1000 * 60 * 60] }] }
                           ]
                        }
                     }
                  }
               }
            }
         }
      }
   },
   { // make average score
      $set: {
         score: {
            $map: {
               input: "$score",
               as: "score",
               in: {
                  range_start: "$$score.range_start",
                  range_end: "$$score.range_end",
                  score: { $avg: "$$score.scores.score" }
               }
            }
         }
      }
   },
   { // suppress empty ranges
      $set: {
         score: {
            $filter: {
               input: "$score",
               cond: { $ne: ["$$this.score", null] }
            }
         }
      }
   }
])

您没有提供所需的输出(作为 JSON),所以我只能猜测。您可能需要做一些外观上的改变。我希望原则应该清楚。

您可以将某些阶段合并为一个阶段。但是,我将它们分开以获得更好的可见性。

$map应该比 工作得更好$reduce,请参阅https://jira.mongodb.org/browse/SERVER-53503


推荐阅读