【问题标题】:MongoDB aggregation group average rating by last 7 days with empty values过去 7 天的 MongoDB 聚合组平均评分,值为空
【发布时间】:2015-10-18 03:44:44
【问题描述】:

我正在尝试查询一个集合并检索过去 7 天(不包括当天)的平均值。在某些或所有的日子里,可能没有平均值。

这是我目前所拥有的:

var dateTill = moment({hour:0,minute:0}).subtract(1, 'days')._d
var dateSevenDaysAgo = moment({hour:0,minute:0}).subtract(7, 'days')._d;

 Rating.aggregate([
   {
     $match:{
      userTo:facebookId,
      timestamp:{$gt:dateSevenDaysAgo,$lt:dateTill}
    }
},
{
  $group:{
    _id:{day:{'$dayOfMonth':'$timestamp'}},
    average:{$avg:'$rating'}
  }
},
{
  $sort:{
    '_id.day':1
  }
}
]

这给了我

[ { _id: { day: 20 }, average: 1 },
  { _id: { day: 22 }, average: 3 },
  { _id: { day: 24 }, average: 5 } ]

我想要得到的是这样的:

[1,,3,,5,,]

按顺序表示最近 7 天的平均值,并且有一个空元素,表示当天没有平均值。

我可以尝试创建一个函数来检测差距在哪里,但是当平均值分布在两个不同的月份时,这将不起作用。例如(July 28,29,30,31,Aug 1,2] - 8 月的日子将被排序到我想要的数组的前面。

有更简单的方法吗?

谢谢!

【问题讨论】:

    标签: node.js mongodb mongoose mongodb-query aggregation-framework


    【解决方案1】:

    人们经常询问“空结果”,而这些想法通常来自他们如何使用 SQL 查询来解决问题。

    但是,虽然为不包含分组键的项目抛出一组“空结果”是“可能的”,但这是一个困难的过程,很像人们使用的 SQL 方法,它只是将这些值抛出人工声明,它确实不是一个非常性能驱动的替代方案。想想用一组制造的钥匙“加入”。效率不高。

    更聪明的方法是直接在客户端 API 中准备好这些结果,而无需发送到服务器。然后聚合输出可以与这些结果“合并”以创建一个完整的集合。

    如何存储要合并的集合取决于您,它只需要一个基本的“哈希表”和查找。但这里有一个使用 nedb 的示例,它允许您维护 MongoDB 的查询和更新思路:

    var async = require('async'),
        mongoose = require('mongoose'),
        DataStore = require('nedb'),
        Schema = mongoose.Schema,
        db = new DataStore();
    
    mongoose.connect('mongodb://localhost/test');
    
    var Test = mongoose.model(
      'Test',
      new Schema({},{ strict: false }),
      "testdata"
    );
    
    var testdata = [
      { "createDate": new Date("2015-07-20"), "value": 2 },
      { "createDate": new Date("2015-07-20"), "value": 4 },
      { "createDate": new Date("2015-07-22"), "value": 4 },
      { "createDate": new Date("2015-07-22"), "value": 6 },
      { "createDate": new Date("2015-07-24"), "value": 6 },
      { "createDate": new Date("2015-07-24"), "value": 8 }
    ];
    
    var startDate = new Date("2015-07-20"),
        endDate = new Date("2015-07-27"),
        oneDay = 1000 * 60 * 60 * 24;
    
    async.series(
      [
        function(callback) {
          Test.remove({},callback);
        },
        function(callback) {
          async.each(testdata,function(data,callback) {
            Test.create(data,callback);
          },callback);
        },
        function(callback) {
          async.parallel(
            [
              function(callback) {
                var tempDate = new Date( startDate.valueOf() );
                async.whilst(
                  function() {
                    return tempDate.valueOf() <= endDate.valueOf();
                  },
                  function(callback) {
                    var day = tempDate.getUTCDate();
                    db.update(
                      { "day": day },
                      { "$inc": { "average": 0 } },
                      { "upsert": true },
                      function(err) {
                        tempDate = new Date(
                          tempDate.valueOf() + oneDay
                        );
                        callback(err);
                      }
                    );
                  },
                  callback
                );
              },
              function(callback) {
                Test.aggregate(
                  [
                    { "$match": {
                      "createDate": {
                        "$gte": startDate,
                        "$lt": new Date( endDate.valueOf() + oneDay )
                      }
                    }},
                    { "$group": {
                      "_id": { "$dayOfMonth": "$createDate" },
                      "average": { "$avg": "$value" }
                    }}
                  ],
                  function(err,results) {
                    if (err) callback(err);
                    async.each(results,function(result,callback) {
                      db.update(
                        { "day": result._id },
                        { "$inc": { "average": result.average } },
                        { "upsert": true },
                        callback
                      )
                    },callback);
                  }
                );
              }
            ],
            callback
          );
        }
      ],
      function(err) {
        if (err) throw err;
        db.find({},{ "_id": 0 }).sort({ "day": 1 }).exec(function(err,result) {
          console.log(result);
          mongoose.disconnect();
        });
      }
    );
    

    给出这个输出:

    [ { day: 20, average: 3 },
      { day: 21, average: 0 },
      { day: 22, average: 5 },
      { day: 23, average: 0 },
      { day: 24, average: 7 },
      { day: 25, average: 0 },
      { day: 26, average: 0 },
      { day: 27, average: 0 } ]
    

    简而言之,“数据存储”是使用nedb 创建的,它的作用基本上与任何 MongoDB 集合相同(具有精简的功能)。然后,您可以为任何结果插入预期的“键”范围和默认值。

    然后运行聚合语句,它只会返回查询集合中存在的键,您只需使用聚合值“更新”在同一键处创建的数据存储。

    为了提高效率,我在parallel 中运行空结果“创建”和“聚合”操作,使用"upsert" 功能和$inc 运算符作为值。这些不会冲突,这意味着创建可以在聚合运行的同时发生,因此不会出现延迟。

    这很容易集成到您的 API 中,因此您可以拥有所需的所有键,包括那些在输出集合中没有用于聚合的数据。

    同样的方法非常适合在 MongoDB 服务器上使用另一个实际集合来处理非常大的结果集。但如果它们非常大,那么无论如何您都应该预先聚合结果,并且只使用标准查询进行采样。

    【讨论】:

    • 这就是为什么堆栈溢出是一个如此重要的资源。非常注意您的回答中的细节,它解决了我的问题!谢谢。
    猜你喜欢
    • 2020-11-09
    • 2016-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-12
    • 1970-01-01
    • 1970-01-01
    • 2018-06-05
    相关资源
    最近更新 更多