sql >> Database teknologi >  >> NoSQL >> MongoDB

MongoDB aggregering - $gruppe efter dato, selvom den ikke eksisterer

I stedet for at prøve at tvinge databasen til at returnere resultater for data, der ikke eksisterer, er det en bedre praksis at generere de tomme data uden for forespørgslen og flette resultaterne ind i dem. På den måde har du dine "0"-indgange, hvor der ikke er nogen data, og tillader databasen at returnere, hvad der er der.

Sammenfletning er en grundlæggende proces med at skabe en hash-tabel med unikke nøgler og blot at erstatte en hvilken som helst af de værdier, der findes i aggregeringen, resulterer i den hash-tabel. I JavaScript passer et grundlæggende objekt godt, da alle nøgler er unikke.

Jeg foretrækker også faktisk at returnere en Date objekt fra aggregeringsresultater ved at bruge datomatematik til at manipulere og "runde" datoen til det påkrævede interval i stedet for at bruge datosammenlægningsoperatorerne. Du kan manipulere datoer ved at bruge $subtract at omdanne værdien til en numerisk tidsstempelrepræsentation ved at trække fra en anden dato med epokedatoværdien og $mod operatør for at få resten og runde datoen af ​​til det nødvendige interval.

I modsætning hertil ved at bruge $add med et lignende epoke-datoobjekt vil en heltalsværdi tilbage til en BSON-dato. Og det er selvfølgelig meget mere effektivt at behandle direkte til $group i stedet for at bruge et separat $project fase, da du bare kan behandle de ændrede datoer direkte ind i grupperingen _id værdi alligevel.

Som et shell-eksempel:

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] })
});

Som vil returnere alle dage i intervallet inklusive 0 værdier, hvor der ikke findes nogen data, f.eks.:

{ "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 }

Bemærker, at alle "dato"-værdier faktisk stadig er BSON-datoer, men bare stringify sådan i outputtet fra .printjson() som en skalmetode.

Et lidt mere kortfattet eksempel kan vises ved hjælp af nodejs hvor du kan bruge operationer som async.parallel at behandle både hash-konstruktionen og aggregeringsforespørgslen på samme tid, såvel som et andet nyttigt værktøj i nedb som implementerer "hash" ved hjælp af funktioner, der er kendt for at bruge en MongoDB-samling. Det viser også, hvordan dette kan skaleres til store resultater ved at bruge en ægte MongoDB-samling, hvis du også ændrede håndteringen til stream-behandling af den returnerede markør fra .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();
      });
    }
  );

});

Dette er meget velegnet til data til diagrammer og grafer. Den grundlæggende procedure er den samme for enhver sprogimplementering og ideelt udført parallelt for at opnå den bedste ydeevne, så asynkrone eller trådede miljøer giver dig en reel bonus, selvom for en lille prøve som denne kan den grundlæggende hash-tabel genereres i hukommelsen meget hurtigt af dit miljø kræver sekventielle handlinger.

Så prøv ikke at tvinge databasen til at gøre dette. Der er bestemt eksempler på SQL-forespørgsler, der gør denne "fletning" på databaseserveren, men det var aldrig rigtig en god idé der og burde virkelig håndteres med en lignende "klient"-fletningsproces, da det bare er at skabe databaseoverhead, hvilket virkelig er t påkrævet.

Det hele er meget effektivt og praktisk til formålet, og det kræver selvfølgelig ikke, at der behandles en separat aggregeringsforespørgsel for hver dag i perioden, hvilket slet ikke ville være effektivt.




  1. Deltag i to samlinger i MongoDB

  2. Hvordan sætter man en billedfil i et json-objekt?

  3. Hadoop Partitioner – Lær det grundlæggende i MapReduce Partitioner

  4. Skub til Laravel-kø fra uden for Laravel (NodeJS)