sql >> Base de Datos >  >> NoSQL >> MongoDB

Agregación de mongo dentro de intervalos de tiempo.

Hay un par de formas de abordar esto según el formato de salida que mejor se adapte a sus necesidades. La nota principal es que con el "marco de agregación" en sí mismo, en realidad no puede devolver algo "lanzado" como una fecha, pero puede obtener valores que se reconstruyen fácilmente en una Date objeto al procesar los resultados en su API.

El primer enfoque es utilizar los "Date Aggregation Operators" disponible para el marco de agregación:

db.collection.aggregate([
    { "$match": {
        "time": { "$gte": startDate, "$lt": endDate }
    }},
    { "$group": {
        "_id": {
            "year": { "$year": "$time" },
            "dayOfYear": { "$dayOfYear": "$time" },
            "hour": { "$hour": "$time" },
            "minute": {
                "$subtract": [
                    { "$minute": "$time" },
                    { "$mod": [ { "$minute": "$time" }, 10 ] }
                ]
            }
        },
        "count": { "$sum": 1 }
    }}
])

Que devuelve una clave compuesta para _id que contiene todos los valores que desea para una "fecha". Alternativamente, si solo está dentro de una "hora", siempre use la parte "minuto" y calcule la fecha real en función de startDate de su selección de rango.

O simplemente puede usar "matemáticas de fecha" para obtener los milisegundos desde "época", que nuevamente se pueden enviar directamente a un constructor de fecha.

db.collection.aggregate([
    { "$match": {
        "time": { "$gte": startDate, "$lt": endDate }
    }},
    { "$group": {
        "_id": {
            "$subtract": [
               { "$subtract": [ "$time", new Date(0) ] },
               { "$mod": [
                   { "$subtract": [ "$time", new Date(0) ] },
                   1000 * 60 * 10
               ]}
            ]
        },
        "count": { "$sum": 1 }
    }}
])

En todos los casos lo que no quiero hacer es usar $project antes de aplicar $group . Como "etapa de canalización", $project debe "recorrer" todos los documentos seleccionados y "transformar" el contenido.

Esto lleva tiempo y se suma al total de ejecución de la consulta. Simplemente puede aplicar al $group directamente como se ha mostrado.

O si eres realmente "puro" acerca de una Date objeto devuelto sin procesamiento posterior, siempre puede usar "mapReduce" , ya que las funciones de JavaScript en realidad permiten la refundición como una fecha, pero más lento que el marco de agregación y, por supuesto, sin una respuesta del cursor:

db.collection.mapReduce(
   function() {
       var date = new Date(
           this.time.valueOf() 
           - ( this.time.valueOf() % ( 1000 * 60 * 10 ) )
       );
       emit(date,1);
   },
   function(key,values) {
       return Array.sum(values);
   },
   { "out": { "inline": 1 } }
)

Sin embargo, su mejor apuesta es usar la agregación, ya que transformar la respuesta es bastante fácil:

db.collection.aggregate([
    { "$match": {
        "time": { "$gte": startDate, "$lt": endDate }
    }},
    { "$group": {
        "_id": {
            "year": { "$year": "$time" },
            "dayOfYear": { "$dayOfYear": "$time" },
            "hour": { "$hour": "$time" },
            "minute": {
                "$subtract": [
                    { "$minute": "$time" },
                    { "$mod": [ { "$minute": "$time" }, 10 ] }
                ]
            }
        },
        "count": { "$sum": 1 }
    }}
]).forEach(function(doc) {
    doc._id = new Date(doc._id);
    printjson(doc);
})

Y luego tiene su salida de agrupación de intervalos con Date real objetos.