Esta é mais uma questão de como você espera que a saída se pareça, pois qualquer resultado agregado precisa essencialmente ser agrupado no nível mais baixo e, em seguida, agrupar progressivamente em "grãos" mais altos até que o nível mais alto ( "mês") seja alcançado. Esse tipo de dado implica em dados agrupados por "mês" em última análise, a menos que você os divida de outra forma.
Em essência, progressivamente
$group
:db.collection.aggregate([
// First total per day. Rounding dates with math here
{ "$group": {
"_id": {
"$add": [
{ "$subtract": [
{ "$subtract": [ "$createdAt", new Date(0) ] },
{ "$mod": [
{ "$subtract": [ "$createdAt", new Date(0) ] },
1000 * 60 * 60 * 24
]}
]},
new Date(0)
]
},
"week": { "$first": { "$week": "$createdAt" } },
"month": { "$first": { "$month": "$createdAt" } },
"total": { "$sum": "$num" }
}},
// Then group by week
{ "$group": {
"_id": "$week",
"month": { "$first": "$month" },
"days": {
"$push": {
"day": "$_id",
"total": "$total"
}
},
"total": { "$sum": "$total" }
}},
// Then group by month
{ "$group": {
"_id": "$month",
"weeks": {
"$push": {
"week": "$_id",
"total": "$total",
"days": "$days"
}
},
"total": { "$sum": "$total" }
}}
])
Portanto, cada nível após o primeiro que soma por dia é progressivamente inserido no conteúdo da matriz para seu valor de "arredondamento" e os totais também são somados nesse nível.
Se você quiser uma saída mais plana com um registro por dia contendo seus totais semanais e mensais, bem como o total do dia, basta anexar dois
$unwind
instruções para o final do pipeline:{ "$unwind": "$weeks" },
{ "$unwind": "$weeks.days" }
E opcionalmente
$project
os campos "pontilhados" para algo mais simples e legível, se necessário. Se você estiver abrangendo "anos" com isso, inclua essa operação na chave de agrupamento pelo menos a partir do nível "semana" para que você não combine dados de anos diferentes e eles sejam separados.
Também é minha preferência geral usar a "date math" abordagem ao arredondar datas, pois retorna uma
Date
objeto, mas como é usado em outros níveis além de "dia", você pode usar alternadamente o operadores de agregação de data
em vez de. Não há necessidade de
mapReduce
como isso é bastante intuitivo e há uma quantidade finita de dias em um mês, o que significa que o limite de BSON ao aninhar matrizes no conteúdo durante a agregação não será quebrado.