agrupar por fechas en mongodb

Estoy trabajando en un proyecto en el que estoy rastreando la cantidad de clics en un tema.

Estoy usando mongodb y tengo que agrupar el número de clics por fecha (quiero agrupar datos por 15 días).

Tengo almacenamiento de datos en el siguiente formato en 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" } 

Quiero agrupar el número de clics en el tema: abc por días (durante 15 días) … sé cómo agruparlo, pero ¿cómo puedo agruparlo por fecha que está almacenado en mi base de datos?

Estoy buscando resultados en el siguiente formato

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

He escrito el código, pero solo funcionará si la fecha está en cadena (el código está aquí http://pastebin.com/2wm1n1ix ) … por favor, guíame cómo lo agrupo

Nueva respuesta usando el marco de agregación de Mongo

Después de que esta pregunta fue hecha y respondida, 10gen lanzó Mongodb versión 2.2 con un marco de agregación, que ahora es la mejor manera de hacer este tipo de consulta. Esta consulta es un poco desafiante porque desea agrupar por fecha y los valores almacenados son marcas de tiempo, por lo que debe hacer algo para convertir las marcas de tiempo en fechas que coincidan. A los fines del ejemplo, solo escribiré una consulta que obtenga los recuentos correctos.

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

Esto devolverá algo así como:

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

_id usar $match para limitar la consulta al rango de fechas que le interesa y $project para cambiar el nombre de _id hasta la date . Cómo convertir el día del año a una fecha queda como ejercicio para el lector. 🙂

10gen tiene una útil tabla de conversión de SQL a Mongo Agregación que vale la pena marcar. También hay un artículo específico sobre los operadores de agregación de fechas .

Un poco más elegante, puede usar:

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

que le proporcionará los últimos 15 días y devolverá alguna fecha y hora dentro de cada día en el campo de date . Por ejemplo:

 [ { "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") } ] 

Respuesta tardía, pero para el registro (para cualquier otra persona que llegue a esta página): Deberá usar el argumento ‘keyf’ en lugar de ‘key’, ya que su clave va a ser en realidad una función de la fecha en el evento (es decir, el “día” extraído de la fecha) y no la fecha misma. Esto debería hacer lo que estás buscando:

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

Para obtener más información, consulte la página de documentación de MongoDB sobre agregación y grupo: http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group

Esto puede ayudar

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

Todavía no he trabajado tanto con MongoDB, así que no estoy completamente seguro. ¿Pero no puedes usar Javascript completo?
Para que pueda analizar su fecha con la clase de Date Javascript, cree su fecha para el día y establezca como clave una propiedad de “salida”. Y siempre agregue uno si la clave ya existe, de lo contrario, cree una nueva con valor = 1 (primer clic). Debajo está su código con función de reducción adaptada (¡código no probado!):

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

Otra respuesta tardía, pero aún. Entonces, si quieres hacerlo en una sola iteración y obtener la cantidad de clics agrupados por fecha y tema, puedes usar el siguiente código:

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

Además, si desea optimizar la consulta como se sugiere, puede usar un valor entero para la fecha (sugerencia: use valueOf (), para la fecha clave en lugar de la cadena, aunque para mis ejemplos, la velocidad era la misma.

Además, siempre es conveniente verificar los documentos de MongoDB regularmente, porque siguen agregando nuevas funciones todo el tiempo. Por ejemplo, con el nuevo marco de agregación, que se lanzará en la versión 2.2, puede obtener los mismos resultados mucho más fácilmente http://docs.mongodb.org/manual/applications/aggregation/

gracias por @mindthief, tu respuesta ayuda a resolver mi problema hoy. La función a continuación puede agruparse por día un poco más fácil, la esperanza puede ayudar a los demás.

 /** * 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'}) 

Si desea que un rechazo de fecha sea devuelto directamente

Luego, en lugar de aplicar los Operadores de agregación de fechas , en su lugar, aplique “Fecha matemática” para redondear el objeto de fecha. Esto a menudo puede ser deseable ya que todos los controladores representan una Fecha BSON en un formato que se usa comúnmente para la manipulación de Fecha para todos los idiomas donde eso es posible:

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

O si, como está implícito en la pregunta que el intervalo de agrupación requerido es “cubos” de 15 días, simplemente aplíquelo al valor numérico en $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 } }} ]) 

La matemática básica aplicada es que cuando $subtract dos objetos Date el resultado devuelto será los milisegundos de diferencia numéricamente. Así que epoch está representado por Date(0) como la base para la conversión en cualquier constructor de lenguaje que tenga.

Con un valor numérico, el “módulo” ( $mod ) se aplica para redondear la fecha (restar el rest de la división) al intervalo requerido. Siendo:

1000 milisegundos x 60 segundos * 60 minutos * 24 horas = 1 día

O

1000 milisegundos x 60 segundos * 60 minutos * 24 horas * 15 días = 15 días

Por lo tanto, es flexible para cualquier intervalo que requiera.

Del mismo modo desde arriba, una operación $add entre un valor “numérico” y un objeto Date devolverá un objeto Date equivalente al valor de los segundos de ambos objetos combinados (epoch es 0, por lo tanto 0 más la diferencia es la fecha convertida).

Fácilmente representado y reproducible en la siguiente lista:

 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(); 

Y ejecutando el segundo ejemplo con intervalos de 15 días:

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

O una distribución similar dependiendo de la fecha actual cuando se ejecuta la lista, y por supuesto los intervalos de 15 días serán consistentes desde la fecha de época.

El uso del método "Matemáticas" es un poco más fácil de sintonizar, especialmente si desea ajustar los períodos de tiempo para diferentes zonas horarias en la salida de agregación, donde puede ajustar de forma similar el valor numérico sumndo / restando la diferencia numérica de UTC.

Por supuesto, esa es una buena solución. Aparte de eso, puedes agrupar las fechas por días como cadenas (como propondrá la respuesta ) o puedes obtener el comienzo de las fechas proyectando el campo de fecha (en agregación) como ese:

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

Te da esto:

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

Tiene algunas ventajas: puede manipular con sus días el tipo de fecha (no número o cadena), le permite utilizar todos los operadores de agregación de fecha en las siguientes operaciones de agregación y le da el tipo de fecha en la salida.