сгруппировать по датам в mongodb - PullRequest
50 голосов
/ 02 марта 2011

Я работаю над проектом, в котором отслеживаю количество кликов по теме.

Я использую mongodb и мне нужно сгруппировать количество кликов по дате (я хочу сгруппировать данные за 15 дней).

У меня есть хранилище данных в следующем формате в mongodb

{ 
   "_id" : ObjectId("4d663451d1e7242c4b68e000"), 
  "date" : "Mon Dec 27 2010 18:51:22 GMT+0000 (UTC)", 
  "topic" : "abc", 
  "time" : "18:51:22"
}
{ 
    "_id" : ObjectId("4d6634514cb5cb2c4b69e000"), 
    "date" : "Mon Dec 27 2010 18:51:23 GMT+0000 (UTC)", 
    "topic" : "bce", 
    "time" : "18:51:23"
}

Я хочу сгруппировать количество кликов по теме: abc по дням (за 15 дней) .. я знаю какчтобы сгруппировать это, но как я могу сгруппировать по дате, которые хранятся в моей базе данных

Я ищу результат в следующем формате

[
  {
    "date" : "date in log",
    "click" : 9 
  },  
  {
    "date" : "date in log",
    "click" : 19
  },  
]

Я написал код, но он будет работать, только если датав строке (код здесь http://pastebin.com/2wm1n1ix) ..., пожалуйста, подскажите, как мне сгруппировать его

Ответы [ 9 ]

66 голосов
/ 24 мая 2013

Новый ответ с использованием структуры агрегирования Mongo

После того, как этот вопрос был задан и получен ответ, 10gen выпустила Mongodb версии 2.2 со структурой агрегации, которая теперь является лучшим способом выполнения такого рода запросов. Этот запрос немного сложен, потому что вы хотите сгруппировать по дате, а сохраненные значения являются временными метками, поэтому вам нужно что-то сделать, чтобы преобразовать временные метки в совпадающие даты. Для целей примера я просто напишу запрос, который получает правильные значения.

db.col.aggregate(
   { $group: { _id: { $dayOfYear: "$date"},
               click: { $sum: 1 } } }
   )

Это вернет что-то вроде:

[
    {
        "_id" : 144,
        "click" : 165
    },
    {
        "_id" : 275,
        "click" : 12
    }
]

Вам нужно использовать $match, чтобы ограничить запрос интересующим вас диапазоном дат, и $project, чтобы переименовать _id в date. Как вы конвертируете день года назад в дату, оставлено читателю в качестве упражнения. : -)

10gen имеет удобную таблицу преобразования SQL в Mongo Aggregation стоит добавить в закладки. Также есть специальная статья о операторах агрегации дат .

Приобретая немного, вы можете использовать:

db.col.aggregate([
  { $group: {
      _id: {
        $add: [
         { $dayOfYear: "$date"}, 
         { $multiply: 
           [400, {$year: "$date"}]
         }
      ]},   
      click: { $sum: 1 },
      first: {$min: "$date"}
    }
  },
  { $sort: {_id: -1} },
  { $limit: 15 },
  { $project: { date: "$first", click: 1, _id: 0} }
])

, который даст вам последние 15 дней и вернет некоторое время в течение каждого дня в поле date. Например:

[
    {
        "click" : 431,
        "date" : ISODate("2013-05-11T02:33:45.526Z")
    },
    {
        "click" : 702,
        "date" : ISODate("2013-05-08T02:11:00.503Z")
    },
            ...
    {
        "click" : 814,
        "date" : ISODate("2013-04-25T00:41:45.046Z")
    }
]
33 голосов
/ 22 февраля 2012

Поздний ответ, но для записи (для всех, кто заходит на эту страницу): вам нужно будет использовать аргумент «keyf» вместо «key», поскольку ваш ключ на самом деле будет функциейдата события (т. е. «день», извлеченный из даты), а не сама дата.Это должно сделать то, что вы ищете:

db.coll.group(
{
    keyf: function(doc) {
        var date = new Date(doc.date);
        var dateKey = (date.getMonth()+1)+"/"+date.getDate()+"/"+date.getFullYear()+'';
        return {'day':dateKey};
    },
    cond: {topic:"abc"},
    initial: {count:0},
    reduce: function(obj, prev) {prev.count++;}
});

Для получения дополнительной информации взгляните на страницу документации MongoDB по агрегации и группе: http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group

18 голосов
/ 28 апреля 2017

Это может помочь

return new Promise(function(resolve, reject) {
db.doc.aggregate(
            [
                { $match: {} },
                { $group: { _id: { $dateToString: { format: "%Y-%m-%d", date: "$date" } }, count: { $sum: 1 } } },
                { $sort: { _id: 1 } }
            ]
        ).then(doc => {
            /* if you need a date object */
            doc.forEach(function(value, index) {
                  doc[index]._id = new Date(value._id);
              }, this);
            resolve(doc);
        }).catch(reject);
}
4 голосов
/ 02 марта 2011

Еще не так много работал с MongoDB, поэтому я не совсем уверен.Но разве вы не можете использовать полный Javascript?
Таким образом, вы можете проанализировать свою дату с помощью класса Javascript Date, создать свою дату для выходного дня и установить ее в качестве ключа в свойстве «out».И всегда добавляйте один, если ключ уже существует, в противном случае создайте его новым со значением = 1 (первый щелчок).Ниже приведен код с адаптированной функцией уменьшения (непроверенный код!):

db.coll.group(
{
   key:{'date':true},
   initial: {retVal: {}},
   reduce: function(doc, prev){
              var date = new Date(doc.date);
              var dateKey = date.getFullYear()+''+date.getMonth()+''+date.getDate();
              (typeof prev.retVal[dateKey] != 'undefined') ? prev.retVal[dateKey] += 1 : prev.retVal[dateKey] = 1;
            }, 
   cond: {topic:"abc"}
}
)
3 голосов
/ 23 мая 2019

На этот вопрос уже есть много ответов, но я не был доволен ни одним из них. MongoDB улучшился за эти годы, и теперь есть более простые способы сделать это. Ответ Джонас Томанга дает правильный ответ, но он слишком сложный.

Если вы используете MongoDB 3.0 или более позднюю версию, вы можете сгруппировать по дате. Я начинаю с агрегации $match, потому что автор также спросил, как ограничить результаты.

db.yourCollection.aggregate([
  { $match: { date: { $gte: ISODate("2019-05-01") } } },
  { $group: { _id: { $dateToString: { format: "%Y-%m-%d", date: "$date"} }, count: { $sum: 1 } } },
  { $sort: { _id: 1} }
])
2 голосов
/ 23 мая 2013

спасибо за @mindthief, ваш ответ поможет решить мою проблему сегодня. Приведенная ниже функция может сгруппировать день немного легче, надежда может помочь другим.

/**
 * group by day
 * @param query document {key1:123,key2:456}
 */
var count_by_day = function(query){
    return db.action.group(
    {
        keyf: function(doc) {
            var date = new Date(doc.time);
            var dateKey = (date.getMonth()+1)+"/"+date.getDate()+"/"+date.getFullYear();
            return {'date': dateKey};
        },
        cond:query,
        initial: {count:0},
        reduce: function(obj, prev) {
          prev.count++;
        }
    });
}

count_by_day({this:'is',the:'query'})
2 голосов
/ 15 июня 2012

Еще один поздний ответ, но все же.Так что, если вы хотите сделать это только за одну итерацию и получить количество кликов, сгруппированных по дате и теме, вы можете использовать следующий код:

db.coll.group(
{
   $keyf : function(doc) {
       return { "date" : doc.date.getDate()+"/"+doc.date.getMonth()+"/"+doc.date.getFullYear(),
                "topic": doc.topic };
    },
    initial: {count:0},
    reduce: function(obj, prev) { prev.count++; }
 })

Также Если вы хотите оптимизировать запрос, как предложено, вы можетеиспользуйте целочисленное значение для даты (подсказка: используйте valueOf (), для контрольной даты вместо String, хотя для моих примеров скорость была одинаковой.

Кроме того, всегда целесообразно регулярно проверять документы MongoDB,потому что они все время добавляют новые функции. Например, с новой структурой Aggregation, которая будет выпущена в версии 2.2, вы можете достичь тех же результатов гораздо проще http://docs.mongodb.org/manual/applications/aggregation/

1 голос
/ 26 февраля 2016

Если вы хотите, чтобы Дата oject возвращалась напрямую

Затем вместо применения Операторов агрегации даты вместо применения "Математика даты", чтобы округлить объект даты. Это часто может быть желательно, так как все драйверы представляют дату BSON в форме, которая обычно используется для манипуляции датой для всех языков, где это возможно:

db.datetest.aggregate([
    { "$group": {
        "_id": {
            "$add": [
                { "$subtract": [
                    { "$subtract": [ "$date", new Date(0) ] },
                    { "$mod": [
                        { "$subtract": [ "$date", new Date(0) ] },
                        1000 * 60 * 60 * 24
                    ]}
                ]},
                new Date(0)
            ]
        },
        "click": { "$sum": 1 }
    }}
])

Или, если в вопросе подразумевается, что требуемый интервал группировки составляет "интервалы" в 15 дней, просто примените это к числовому значению в $mod:

db.datetest.aggregate([
    { "$group": {
        "_id": {
            "$add": [
                { "$subtract": [
                    { "$subtract": [ "$date", new Date(0) ] },
                    { "$mod": [
                        { "$subtract": [ "$date", new Date(0) ] },
                        1000 * 60 * 60 * 24 * 15
                    ]}
                ]},
                new Date(0)
            ]
        },
        "click": { "$sum": 1 }
    }}
])

Основная применяемая математика заключается в том, что когда вы $subtract два Date объекта, возвращаемый результат будет численно выражаться в миллисекундах разности. Таким образом, эпоха представляется Date(0) в качестве основы для преобразования в любой языковой конструктор.

При числовом значении "modulo" ($mod) применяется для округления даты (вычитания остатка из деления) до требуемого интервала. Быть либо:

1000 миллисекунд x 60 секунд * 60 минут * 24 часа = 1 день

или

1000 миллисекунд x 60 секунд * 60 минут * 24 часа * 15 дней = 15 дней

Так что он гибок к любому интервалу, который вам необходим.

По тому же показателю сверху операция $add между «числовым» значением и объектом Date вернет объект Date, эквивалентный значению в миллисекундах обоих объектов вместе (эпоха 0, следовательно, 0 плюс разница - конвертированная дата).

Легко представляется и воспроизводится в следующем листинге:

var now = new Date();
var bulk = db.datetest.initializeOrderedBulkOp();

for ( var x = 0; x < 60; x++ ) {
    bulk.insert({ "date": new Date( now.valueOf() + ( 1000 * 60 * 60 * 24 * x ))});
}

bulk.execute();

И второй пример с 15-дневными интервалами:

{ "_id" : ISODate("2016-04-14T00:00:00Z"), "click" : 12 }
{ "_id" : ISODate("2016-03-30T00:00:00Z"), "click" : 15 }
{ "_id" : ISODate("2016-03-15T00:00:00Z"), "click" : 15 }
{ "_id" : ISODate("2016-02-29T00:00:00Z"), "click" : 15 }
{ "_id" : ISODate("2016-02-14T00:00:00Z"), "click" : 3 }

Или аналогичное распределение в зависимости от текущей даты запуска листинга, и, конечно, 15-дневные интервалы будут согласованы с даты эпохи.

Использование метода "Математика" немного проще в настройке, особенно если вы хотите настроить периоды времени для разных часовых поясов в агрегированном выводе, где вы можете аналогичным образом выполнить числовую регулировку, добавляя / вычитая числовую разницу из UTC.

0 голосов
/ 27 апреля 2017

Конечно, , что - хорошее решение. Кроме того, вы можете сгруппировать даты по дням в виде строк (как , которые отвечают предложить) или вы можете получить начало дат, проецируя поле даты (в агрегации) следующим образом:

{'$project': {
    'start_of_day': {'$subtract': [
        '$date',
        {'$add': [
            {'$multiply': [{'$hour': '$date'}, 3600000]},
            {'$multiply': [{'$minute': '$date'}, 60000]},
            {'$multiply': [{'$second': '$date'}, 1000]},
            {'$millisecond': '$date'}
        ]}
    ]},
}}

Это дает вам это:

{
    "start_of_day" : ISODate("2015-12-03T00:00:00.000Z")
},
{
    "start_of_day" : ISODate("2015-12-04T00:00:00.000Z")
}

У него есть некоторые плюсы: вы можете манипулировать своими днями в типе даты (не числом или строкой), он позволяет вам использовать все операторы агрегирования даты в следующих операциях агрегации и дает вам тип даты на выходе.

...