MongoDB 聚合 - 即使不存在 $group by date

And*_*rew 5 mongodb mongodb-query aggregation-framework

我已经有一个查询,如下所示:

{$match:{
      "when":{$gt: new Date(ISODate().getTime() - 1000 * 60 * 60 * 24 * 30)}
}}, 
{$project:{
      "year":{$year:"$when"}, 
      "month":{$month:"$when"}, 
      "day": {$dayOfMonth:"$when"}
}}, 
{$group:{
      _id:{year:"$year", month:"$month", day:"$day"}, 
      "count":{$sum:1}
}},
{$sort:{
    _id: 1
}}
Run Code Online (Sandbox Code Playgroud)

结果如下所示:

{ "_id" : { "year" : 2015, "month" : 10, "day" : 19 }, "count" : 1 }
{ "_id" : { "year" : 2015, "month" : 10, "day" : 21 }, "count" : 2 }
Run Code Online (Sandbox Code Playgroud)

除了过去 30 天的结果,我怎么能以相同的格式获得结果,即使count是 0?

像这样:

{ "_id" : { "year" : 2015, "month" : 10, "day" : 01 }, "count" : 1 }
{ "_id" : { "year" : 2015, "month" : 10, "day" : 02 }, "count" : 2 }
{ "_id" : { "year" : 2015, "month" : 10, "day" : 03 }, "count" : 0 }
...
{ "_id" : { "year" : 2015, "month" : 10, "day" : 30 }, "count" : 2 }
Run Code Online (Sandbox Code Playgroud)

Bla*_*ven 4

与其尝试强制数据库返回不存在的数据的结果,不如在查询外部生成空白数据并将结果合并到其中。这样,您就可以在没有数据的地方获得“0”条目,并允许数据库返回其中的内容。

合并是创建唯一键的哈希表并简单地替换该哈希表中聚合结果中找到的任何值的基本过程。在 JavaScript 中,基本对象非常适合,因为所有键都是唯一的。

我还更喜欢Date通过使用日期数学来操作并将日期“舍入”到所需的间隔,而不是使用日期聚合运算符,从而实际从聚合结果返回对象。您可以通过使用纪元日期$subtract值从另一个日期中减去该值将值转换为数字时间戳表示来操作日期,并使用运算符$mod获取余数并将日期舍入到所需的间隔。

相反,使用$add类似的纪元日期对象会将整数值转换回 BSON 日期。当然,直接处理比$group使用单独的$project阶段要高效得多,因为无论如何您都可以将修改的日期直接处理到分组_id值中。

作为一个外壳示例:

var sample = 30,
    Days = 30,
    OneDay = ( 1000 * 60 * 60 * 24 ),
    now = Date.now(),
    Today = now - ( now % OneDay ) ,
    nDaysAgo = Today - ( OneDay * Days ),
    startDate = new Date( nDaysAgo ),
    endDate = new Date( Today + OneDay ),
    store = {};

var thisDay = new Date( nDaysAgo );
while ( thisDay < endDate ) {
    store[thisDay] = 0;
    thisDay = new Date( thisDay.valueOf() + OneDay );
}

db.datejunk.aggregate([
    { "$match": { "when": { "$gte": startDate } }},
    { "$group": {
        "_id": {
            "$add": [
                { "$subtract": [
                    { "$subtract": [ "$when", new Date(0) ] },
                    { "$mod": [
                        { "$subtract": [ "$when", new Date(0) ] },
                        OneDay
                    ]}
                ]},
                new Date(0)
            ]
        },
        "count": { "$sum": 1 }
    }}
]).forEach(function(result){
    store[result._id] = result.count;
});

Object.keys(store).forEach(function(k) {
    printjson({ "date": k, "count": store[k] })
});
Run Code Online (Sandbox Code Playgroud)

这将返回时间间隔内的所有日期,包括0不存在数据的值,例如:

{ "date" : "Tue Sep 22 2015 10:00:00 GMT+1000 (AEST)", "count" : 0 }
{ "date" : "Wed Sep 23 2015 10:00:00 GMT+1000 (AEST)", "count" : 1 }
{ "date" : "Thu Sep 24 2015 10:00:00 GMT+1000 (AEST)", "count" : 0 }
{ "date" : "Fri Sep 25 2015 10:00:00 GMT+1000 (AEST)", "count" : 1 }
{ "date" : "Sat Sep 26 2015 10:00:00 GMT+1000 (AEST)", "count" : 1 }
{ "date" : "Sun Sep 27 2015 10:00:00 GMT+1000 (AEST)", "count" : 0 }
{ "date" : "Mon Sep 28 2015 10:00:00 GMT+1000 (AEST)", "count" : 1 }
{ "date" : "Tue Sep 29 2015 10:00:00 GMT+1000 (AEST)", "count" : 1 }
{ "date" : "Wed Sep 30 2015 10:00:00 GMT+1000 (AEST)", "count" : 0 }
{ "date" : "Thu Oct 01 2015 10:00:00 GMT+1000 (AEST)", "count" : 1 }
{ "date" : "Fri Oct 02 2015 10:00:00 GMT+1000 (AEST)", "count" : 2 }
{ "date" : "Sat Oct 03 2015 10:00:00 GMT+1000 (AEST)", "count" : 0 }
{ "date" : "Sun Oct 04 2015 11:00:00 GMT+1100 (AEST)", "count" : 1 }
{ "date" : "Mon Oct 05 2015 11:00:00 GMT+1100 (AEDT)", "count" : 0 }
{ "date" : "Tue Oct 06 2015 11:00:00 GMT+1100 (AEDT)", "count" : 1 }
{ "date" : "Wed Oct 07 2015 11:00:00 GMT+1100 (AEDT)", "count" : 2 }
{ "date" : "Thu Oct 08 2015 11:00:00 GMT+1100 (AEDT)", "count" : 2 }
{ "date" : "Fri Oct 09 2015 11:00:00 GMT+1100 (AEDT)", "count" : 1 }
{ "date" : "Sat Oct 10 2015 11:00:00 GMT+1100 (AEDT)", "count" : 1 }
{ "date" : "Sun Oct 11 2015 11:00:00 GMT+1100 (AEDT)", "count" : 1 }
{ "date" : "Mon Oct 12 2015 11:00:00 GMT+1100 (AEDT)", "count" : 0 }
{ "date" : "Tue Oct 13 2015 11:00:00 GMT+1100 (AEDT)", "count" : 3 }
{ "date" : "Wed Oct 14 2015 11:00:00 GMT+1100 (AEDT)", "count" : 2 }
{ "date" : "Thu Oct 15 2015 11:00:00 GMT+1100 (AEDT)", "count" : 2 }
{ "date" : "Fri Oct 16 2015 11:00:00 GMT+1100 (AEDT)", "count" : 0 }
{ "date" : "Sat Oct 17 2015 11:00:00 GMT+1100 (AEDT)", "count" : 3 }
{ "date" : "Sun Oct 18 2015 11:00:00 GMT+1100 (AEDT)", "count" : 0 }
{ "date" : "Mon Oct 19 2015 11:00:00 GMT+1100 (AEDT)", "count" : 0 }
{ "date" : "Tue Oct 20 2015 11:00:00 GMT+1100 (AEDT)", "count" : 0 }
{ "date" : "Wed Oct 21 2015 11:00:00 GMT+1100 (AEDT)", "count" : 2 }
{ "date" : "Thu Oct 22 2015 11:00:00 GMT+1100 (AEDT)", "count" : 1 }
Run Code Online (Sandbox Code Playgroud)

.printjson()请注意,所有“日期”值实际上仍然是 BSON 日期,但只是像shell 方法的输出中那样进行字符串化。

可以使用以下示例显示更简洁的示例,nodejs您可以在其中使用同时async.parallel处理哈希构造和聚合查询等操作,以及另一个有用的实用程序,其中nedb使用熟悉使用 MongoDB 的函数来实现“哈希”收藏。它还展示了如何通过使用真实的 MongoDB 集合来扩展大型结果,如果您还将返回游标的处理方式更改为流处理.aggregate()

var async = require('async'),
    mongodb = require('mongodb'),
    MongoClient = mongodb.MongoClient,
    nedb = require('nedb'),
    DataStore = new nedb();

// Setup vars
var sample = 30,
    Days = 30,
    OneDay = ( 1000 * 60 * 60 * 24 ),
    now = Date.now(),
    Today = now - ( now % OneDay ) ,
    nDaysAgo = Today - ( OneDay * Days ),
    startDate = new Date( nDaysAgo ),
    endDate = new Date( Today + OneDay );

MongoClient.connect('mongodb://localhost/test',function(err,db) {

  var coll = db.collection('datejunk');

  async.series(
    [
      // Clear test collection
      function(callback) {
        coll.remove({},callback)
      },

      // Generate a random sample
      function(callback) {
        var bulk = coll.initializeUnorderedBulkOp();

        while (sample--) {
          bulk.insert({
            "when": new Date(
              Math.floor(
                Math.random()*(Today-nDaysAgo+OneDay)+nDaysAgo
              )
            )
          });
        }
        bulk.execute(callback);
      },

      // Aggregate data and dummy data
      function(callback) {
        console.log("generated");
        async.parallel(
          [
            // Dummy data per day
            function(callback) {
              var thisDay = new Date( nDaysAgo );
              async.whilst(
                function() { return thisDay < endDate },
                function(callback) {
                  DataStore.update(
                    { "date": thisDay },
                    { "$inc": { "count": 0 } },
                    { "upsert": true },
                    function(err) {
                      thisDay = new Date( thisDay.valueOf() + OneDay );
                      callback(err);
                    }
                  );
                },
                callback
              );
            },
            // Aggregate data in collection
            function(callback) {
              coll.aggregate(
                [
                  { "$match": { "when": { "$gte": startDate } } },
                  { "$group": {
                    "_id": {
                      "$add": [
                        { "$subtract": [
                          { "$subtract": [ "$when", new Date(0) ] },
                          { "$mod": [
                            { "$subtract": [ "$when", new Date(0) ] },
                            OneDay
                          ]}
                        ]},
                        new Date(0)
                      ]
                    },
                    "count": { "$sum": 1 }
                  }}
                ],
                function(err,results) {
                  if (err) callback(err);
                  async.each(results,function(result,callback) {
                    DataStore.update(
                      { "date": result._id },
                      { "$inc": { "count": result.count } },
                      { "upsert": true },
                      callback
                    );
                  },callback);
                }
              );
            }
          ],
          callback
        );
      }
    ],
    // Return result or error
    function(err) {
      if (err) throw err;
      DataStore.find({},{ "_id": 0 })
        .sort({ "date": 1 })
        .exec(function(err,results) {
        if (err) throw err;
        console.log(results);
        db.close();
      });
    }
  );

});
Run Code Online (Sandbox Code Playgroud)

这非常适合图表和图形的数据。任何语言实现的基本过程都是相同的,并且最好在并行处理中完成以获得最佳性能,因此异步或线程环境会给您带来真正的好处,即使对于像这样的小样本,基本哈希表可以在内存中非常快速地生成您的环境需要顺序操作。

因此,不要尝试强制数据库执行此操作。当然有一些 SQL 查询的例子在数据库服务器上进行这种“合并”,但这从来都不是一个好主意,应该用类似的“客户端”合并过程来处理,因为它只是创建数据库开销,而这实际上并不是“ t 需要。

这一切都非常高效且实用,当然,它不需要为该时间段内的每一天处理单独的聚合查询,这根本就没有效率。