MongoDB 聚合组最近 7 天的平均评分(空值)

MongoDB aggregation group average rating by last 7 days with empty values

我正在尝试查询集合并检索过去 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] - 八月的日子将被排序到我想要的数组的前面。

有更简单的方法吗?

谢谢!

人们经常询问 "empty results",他们的想法通常来自他们如何通过 SQL 查询来解决问题。

但是虽然 "possible" 为不包含分组键的项目抛出一组 "empty results",但这是一个困难的过程,很像 SQL 方法使用,它只是人为地将这些值放入语句中,它确实不是一个非常受性能驱动的替代方案。想想 "join" 使用一组制造的密钥。效率不高。

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

无论您想要存储要合并的集合取决于您,它只需要基本的 "hash table" 和查找。但这里有一个使用 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 } ]

简而言之,"datastore" 是用 nedb 创建的,它的行为基本上与任何 MongoDB 集合相同(具有精简功能)。然后,您可以为任何结果插入 "keys" 范围内的预期值和默认值。

然后 运行 你的聚合语句,它只会 return 查询集合中存在的键,你只需 "update" 在与聚合值。

为了提高效率,我 运行 空结果 "creation" 和 parallel, utilizing "upsert" functionallity and the $inc 运算符中的 "aggregation" 操作值。这些不会冲突,这意味着创建可以在聚合的同时发生运行,所以没有延迟。

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

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