MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Agregação do Mongo em intervalos de tempo


Existem algumas maneiras de abordar isso, dependendo de qual formato de saída melhor atende às suas necessidades. A observação principal é que com o "estrutura de agregação" em si, você não pode realmente retornar algo "cast" como uma data, mas pode obter valores que são facilmente reconstruídos em uma Date objeto ao processar resultados em sua API.

A primeira abordagem é usar os "Operadores de agregação de data" disponível para a estrutura de agregação:
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 retorna uma chave composta para _id contendo todos os valores que você deseja para uma "data". Alternativamente, se estiver sempre dentro de uma "hora", use a parte "minuto" e calcule a data real com base no startDate de sua seleção de intervalo.

Ou você pode simplesmente usar "Date math" para obter os milissegundos desde "epoch", que podem novamente ser alimentados diretamente a um construtor de datas.
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 }
    }}
])

Em todos os casos, o que você não faz quero fazer é usar $project antes de aplicar $group . Como um "estágio de pipeline", $project deve "circular" por todos os documentos selecionados e "transformar" o conteúdo.

Isso leva tempo , e adiciona ao total de execução da consulta. Você pode simplesmente aplicar ao $group diretamente como foi mostrado.

Ou se você é realmente "puro" sobre uma Date objeto sendo retornado sem pós-processamento, você sempre poderá usar "mapReduce" , já que as funções JavaScript realmente permitem a reformulação como uma data, mas mais lenta que a estrutura de agregação e, claro, sem uma resposta do 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 } }
)

Sua melhor aposta é usar a agregação, pois transformar a resposta é 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);
})

E então você tem sua saída de agrupamento de intervalos com Date real objetos.