Conforme declarado, o processamento de documentos como este não é possível com a estrutura de agregação, a menos que você realmente forneça todas as chaves, como:
db.events.aggregate([
{ "$group": {
"_id": "$app_id",
"event_count": { "$sum": "$event_count" },
"0": { "$sum": "$event_count_per_type.0" },
"10": { "$sum": "$event_count_per_type.10" }
"20": { "$sum": "$event_count_per_type.20" }
"30": { "$sum": "$event_count_per_type.30" }
}}
])
Mas é claro que você precisa especificar explicitamente todos chave que deseja trabalhar. Isso vale tanto para a estrutura de agregação quanto para as operações gerais de consulta no MongoDB, pois para acessar elementos notados neste formulário de "subdocumento" você precisa especificar o "caminho exato" para o elemento para fazer qualquer coisa com ele.
A estrutura de agregação e as consultas gerais não têm o conceito de "percurso", o que significa que não podem processar "cada chave" de um documento. Isso requer uma construção de linguagem para fazer o que não é fornecido nessas interfaces.
De um modo geral, porém, usar um "nome de chave" como um ponto de dados em que seu nome realmente representa um "valor" é um pouco "anti-padrão". Uma maneira melhor de modelar isso seria usar uma matriz e representar seu "tipo" como um valor por si só:
{
"app_id": "DHJFK67JDSJjdasj909",
"date: ISODate("2014-08-07T00:00:00.000Z"),
"event_count": 32423,
"events": [
{ "type": 0, "value": 322 },
{ "type": 10, "value": 4234 },
{ "type": 20, "value": 653 },
{ "type": 30, "value": 7562 }
]
}
Observando também que a "data" agora é um objeto de data adequado em vez de uma string, o que também é uma boa prática. Esse tipo de dados é fácil de processar com a estrutura de agregação:
db.events.aggregate([
{ "$unwind": "$events" },
{ "$group": {
"_id": {
"app_id": "$app_id",
"type": "$events.type"
},
"event_count": { "$sum": "$event_count" },
"value": { "$sum": "$value" }
}},
{ "$group": {
"_id": "$_id.app_id",
"event_count": { "$sum": "$event_count" },
"events": { "$push": { "type": "$_id.type", "value": "$value" } }
}}
])
Isso mostra um agrupamento de dois estágios que primeiro obtém os totais por "tipo" sem especificar cada "chave" já que você não precisa mais e, em seguida, retorna como um único documento por "app_id" com os resultados em uma matriz como foram armazenados originalmente. Esse formulário de dados geralmente é muito mais flexível para observar certos "tipos" ou até mesmo os "valores" dentro de um determinado intervalo.
Onde você não pode alterar a estrutura, sua única opção é mapReduce. Isso permite que você "codifique" a passagem das chaves, mas como isso requer interpretação e execução de JavaScript, não é tão rápido quanto a estrutura de agregação:
db.events.mapReduce(
function() {
emit(
this.app_id,
{
"event_count": this.event_count,
"event_count_per_type": this.event_count_per_type
}
);
},
function(key,values) {
var reduced = { "event_count": 0, "event_count_per_type": {} };
values.forEach(function(value) {
for ( var k in value.event_count_per_type ) {
if ( !redcuced.event_count_per_type.hasOwnProperty(k) )
reduced.event_count_per_type[k] = 0;
reduced.event_count_per_type += value.event_count_per_type;
}
reduced.event_count += value.event_count;
})
},
{
"out": { "inline": 1 }
}
)
Isso essencialmente percorrerá e combinará as "chaves" e somará os valores de cada uma encontrada.
Então suas opções são:
- Altere a estrutura e trabalhe com consultas e agregação padrão.
- Permaneça com a estrutura e exija processamento JavaScript e mapReduce.
Depende das suas necessidades reais, mas na maioria dos casos a reestruturação traz benefícios.