MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Agregacja MongoDB - $grupuj według daty, nawet jeśli nie istnieje

Zamiast próbować zmusić bazę danych do zwracania wyników dla danych, które nie istnieją, lepszą praktyką jest generowanie pustych danych zewnętrznych względem zapytania i scalanie wyników z nimi. W ten sposób masz swoje wpisy "0" tam, gdzie nie ma danych i pozwalasz bazie danych na zwrócenie tego, co tam jest.

Scalanie to podstawowy proces tworzenia zaszyfrowanej tabeli unikalnych kluczy i proste zastąpienie dowolnych wartości znalezionych w agregacji w tej tabeli zaszyfrowanej. W JavaScript podstawowy obiekt pasuje, a wszystkie klucze są unikalne.

Wolę również zwracać Date obiekt z wyników agregacji przy użyciu matematyki daty do manipulowania i „zaokrąglania” daty do wymaganego interwału, zamiast używania operatorów agregacji dat. Możesz manipulować datami, używając $subtract aby przekształcić wartość w liczbową reprezentację znacznika czasu, odejmując od innej daty wartość daty epoki oraz $mod operatora, aby uzyskać resztę i zaokrąglić datę do wymaganego przedziału.

W przeciwieństwie do tego, używając $add z podobnym obiektem daty epoki zamieni wartość całkowitą z powrotem na datę BSON. I oczywiście znacznie bardziej wydajne jest przetwarzanie bezpośrednio do $group zamiast używać oddzielnego $project etap, ponieważ możesz po prostu przetworzyć zmodyfikowane daty bezpośrednio w grupie _id wartość mimo wszystko.

Jako przykład powłoki:

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

Który zwróci wszystkie dni w przedziale, w tym 0 wartości, w przypadku których nie ma danych, np.:

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

Zauważając, że wszystkie wartości "dat" są w rzeczywistości nadal datami BSON, ale po prostu skróć w ten sposób w danych wyjściowych z .printjson() jako metoda powłoki.

Nieco bardziej zwięzły przykład można pokazać za pomocą nodejs gdzie możesz używać operacji takich jak async.parallel do przetwarzania zarówno konstrukcji skrótu, jak i zapytania agregującego w tym samym czasie, a także innego użytecznego narzędzia w nedb który implementuje "hash" używając funkcji znanych z używania kolekcji MongoDB. Pokazuje również, jak można to skalować dla dużych wyników, używając prawdziwej kolekcji MongoDB, jeśli zmieniłeś również obsługę przetwarzania strumieniowego zwracanego kursora z .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();
      });
    }
  );

});

Jest to bardzo odpowiednie dla danych do wykresów i wykresów. Podstawowa procedura jest taka sama dla każdej implementacji języka i najlepiej jest przeprowadzana w przetwarzaniu równoległym w celu uzyskania najlepszej wydajności, więc środowiska asynchroniczne lub wątkowe dają prawdziwą premię, nawet jeśli w przypadku takiej małej próbki podstawowa tablica mieszająca może być bardzo szybko wygenerowana w pamięci twojego środowiska wymaga sekwencyjnych operacji.

Więc nie próbuj zmuszać bazy danych do tego. Z pewnością istnieją przykłady zapytań SQL, które wykonują to „scalanie” na serwerze bazy danych, ale nigdy nie było to naprawdę świetnym pomysłem i naprawdę powinno być obsługiwane za pomocą podobnego procesu scalania „klienta”, ponieważ jest to tylko tworzenie narzutu bazy danych, który tak naprawdę nie jest wymagane.

Wszystko to jest bardzo wydajne i praktyczne w tym celu i oczywiście nie wymaga przetwarzania oddzielnego zapytania agregującego dla każdego dnia w okresie, co w ogóle nie byłoby wydajne.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Struktura agregacji Mongo:jaki jest poziom blokady ostatniej operacji $out?

  2. geoNear zwraca nieprawidłową odległość

  3. Zwracanie pól niestandardowych w MongoDB

  4. Mongo - zapytanie, osadzony dokument nie pasuje z wyjątkiem notacji kropkowej

  5. Mongoose pobiera ObjectId z tablicy