MongoDB聚合组平均评分过去7天,空值

时间:2015-07-27 19:42:05

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

我尝试查询集合并检索除当前日期之外的最近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天的顺序,并且有一个空元素,当天没有平均值。

我可以尝试制作一个能够检测出差距在哪里的功能,但是当平均值分布在两个不同的月份时,这种功能不会起作用。例如(8月28,29,30,31,8月1日) - 八月的日子将被分类到我想要的阵列的前面。

有更简单的方法吗?

谢谢!

1 个答案:

答案 0 :(得分:4)

人们询问"空结果"很多时候,这种想法通常来自于他们如何通过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 } ]

简而言之,&#34;数据存储&#34;是使用nedb创建的,它基本上与任何MongoDB集合相同(具有精简功能)。然后,您插入一系列&#34;键&#34;任何结果的预期值和默认值。

然后运行您的聚合语句,它只返回查询集合中存在的键,您只需&#34;更新&#34;创建的数据存储区在具有聚合值的同一个密钥上。

为了提高效率,我正在运行空结果&#34;创建&#34;和#34;聚合&#34; parallel中的操作,使用"upsert" functionallity和值$inc运算符。这些不会发生冲突,这意味着创建可以在聚合运行的同时发生,因此没有延迟。

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

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