2015-10-21 2 views
3

У меня есть запрос, который уже выглядит следующим образом:

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

Результат выглядит следующим образом:

{ "_id" : { "year" : 2015, "month" : 10, "day" : 19 }, "count" : 1 } 
{ "_id" : { "year" : 2015, "month" : 10, "day" : 21 }, "count" : 2 } 

Как я мог получить результат в том же формате, кроме наличия его в течение последних 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 } 
+1

Я не думаю, что это было бы возможно. Возможно, вы захотите повторить все дни в месяц, а затем поместите счеты из mongodb. –

+0

Можете ли вы снова объяснить этот вопрос? для меня оба выхода одинаковы, но у второго есть больше результатов. Что ты хочешь делать? – sergiuz

+0

@SergiuZaharie Я хочу включить объекты в результирующий набор, где поле счетчика равно 0. – Andrew

ответ

1

Вместо того, чтобы заставить базу данных для возвращения результатов для данных, не существует, это лучшая практика для создания пустых данных внешних по отношению к запросу и сливаться результаты в них. Таким образом, у вас есть свои записи «0», где нет данных, и разрешить базе данных возвращать то, что есть.

Слияние - это основной процесс создания хеш-таблицы уникальных ключей и просто замена любых значений, найденных в результатах агрегации, в этой хэш-таблице. В JavaScript базовый объект подходит, так как все ключи уникальны.

Я также предпочитаю фактически возвращать объект Date из результатов агрегирования, используя математику даты, чтобы манипулировать и «округлять» дату до требуемого интервала, а не использовать агенты агрегации даты. Вы можете манипулировать датами, используя $subtract, чтобы превратить значение в числовое представление временной метки путем вычитания из другой даты с использованием значения даты эпохи и оператора $mod, чтобы получить остаток и округлить дату до требуемого интервала.

В отличие от этого, используя $add с аналогичным объектом даты эпохи, значение integer возвращается в дату 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 требуется.

Это очень эффективная и практичная цель, и, конечно же, она не требует обработки отдельного запроса на агрегирование для каждого дня в периоде, что было бы неэффективным вообще.

Смежные вопросы