MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Агрегиране на MongoDB - $group по дата, дори и да не съществува

Вместо да се опитвате да принудите базата данни да върне резултати за данни, които не съществуват, по-добра практика е да генерирате празни данни, външни за заявката, и да обедините резултатите в тях. По този начин имате вашите "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] })
});

Което ще върне всички дни в интервала, включително 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 }

Отбелязвайки, че всички стойности на „дата“ всъщност все още са BSON дати, но просто стрингирайте така в изхода от .printjson() като метод на обвивка.

Малко по-сбит пример може да бъде показан с помощта на 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();
      });
    }
  );

});

Това е много подходящо за данни за диаграми и графики. Основната процедура е една и съща за всяко внедряване на език и в идеалния случай се извършва в паралелна обработка за най-добра производителност, така че асинхронните или резбовани среди ви дават истински бонус, въпреки че за малка извадка като тази основната хеш таблица може да се генерира в паметта много бързо на вашата среда изисква последователни операции.

Така че не се опитвайте да принудите базата данни да направи това. Със сигурност има примери за SQL заявки, които правят това „сливане“ на сървъра на базата данни, но това никога не е било страхотна идея там и наистина трябва да се обработва с подобен „клиентски“ процес на сливане, тъй като това е просто създаване на излишни разходи за база данни, което наистина е Изисква се t.

Всичко е много ефективно и практично за целта и, разбира се, не изисква обработка на отделна заявка за агрегиране за всеки ден от периода, което изобщо не би било ефективно.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Лоша производителност на агрегиране при търсене

  2. Как да съхранявате дата и час в Meteor за заявки за обхват?

  3. MongoDB Актуализиране на вложен масив

  4. Как да запазите часова зона правилно с Ruby и MongoId?

  5. Сравняване на дати в Mongodb с C# LINQ драйвер