MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Aggregazione Mongo entro intervalli di tempo

Ci sono un paio di modi per affrontare questo problema a seconda del formato di output più adatto alle tue esigenze. La nota principale è che con il "framework di aggregazione" di per sé, non puoi effettivamente restituire qualcosa di "cast" come data, ma puoi ottenere valori che possono essere facilmente ricostruiti in una Date oggetto durante l'elaborazione dei risultati nella tua API.

Il primo approccio consiste nell'usare "Date Aggregation Operators" a disposizione del framework di aggregazione:

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

Che restituisce una chiave composta per _id contenente tutti i valori desiderati per una "data". In alternativa, se solo entro "un'ora", usa sempre la parte "minuto" e calcola la data effettiva in base a startDate della tua gamma di selezione.

Oppure puoi semplicemente usare "Date math" per ottenere i millisecondi da "epoca" che possono essere nuovamente inviati direttamente a un costruttore di 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 }
    }}
])

In tutti i casi cosa non quello che vuoi fare è usare $project prima di applicare effettivamente $group . Come "fase della pipeline", $project deve "passare in ciclo" tutti i documenti selezionati e "trasformare" il contenuto.

Questo richiede tempo e si aggiunge al totale di esecuzione della query. Puoi semplicemente candidarti al $group direttamente come è stato mostrato.

O se sei davvero "puro" su una Date oggetto restituito senza elaborazione successiva, puoi sempre utilizzare "mapReduce" , poiché le funzioni JavaScript consentono effettivamente la rifusione come data, ma più lenta del framework di aggregazione e ovviamente senza una risposta del cursore:

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

La soluzione migliore è tuttavia utilizzare l'aggregazione, poiché trasformare la risposta è abbastanza semplice:

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

E poi hai l'output di raggruppamento di intervalli con Date reale oggetti.