MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Agrégation Mongo dans des intervalles de temps

Il existe plusieurs façons d'aborder cela en fonction du format de sortie qui convient le mieux à vos besoins. La remarque principale est qu'avec le "aggregate framework" lui-même, vous ne pouvez pas réellement renvoyer quelque chose "cast" comme une date, mais vous pouvez obtenir des valeurs qui sont facilement reconstruites dans une Date objet lors du traitement des résultats dans votre API.

La première approche consiste à utiliser les "Date Aggregation Operators" disponibles pour le framework d'agrégation :

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

Qui renvoie une clé composite pour _id contenant toutes les valeurs souhaitées pour une "date". Alternativement, si juste dans une "heure", utilisez toujours la partie "minute" et calculez la date réelle en fonction de startDate de votre sélection de gamme.

Ou vous pouvez simplement utiliser des "calculs de date" simples pour obtenir les millisecondes depuis "l'époque" qui peuvent à nouveau être directement transmises à un constructeur de date.

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

Dans tous les cas ce que vous ne faites pas voulez faire est d'utiliser $project avant d'appliquer réellement $group . En tant qu'"étape de pipeline", $project doit "faire défiler" tous les documents sélectionnés et "transformer" le contenu.

Cela prend du temps , et s'ajoute au total d'exécution de la requête. Vous pouvez simplement postuler au $group directement comme cela a été montré.

Ou si vous êtes vraiment "pur" à propos d'une Date objet renvoyé sans post-traitement, vous pouvez toujours utiliser "mapReduce" , puisque les fonctions JavaScript autorisent en fait la refonte en date, mais plus lentement que le framework d'agrégation et bien sûr sans réponse du curseur :

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

Votre meilleur pari est cependant d'utiliser l'agrégation, car transformer la réponse est assez facile :

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

Et puis vous avez votre sortie de regroupement d'intervalles avec la vraie Date objets.