Pessoalmente eu não sou um grande fã de transformar "dados" como os nomes das chaves em um resultado. Os princípios da estrutura de agregação tendem a concordar, pois esse tipo de operação também não é suportado.
Portanto, a preferência pessoal é manter "dados" como "dados" e aceitar que a saída processada é realmente melhor e mais lógica para um design de objeto consistente:
db.people.aggregate([
{ "$group": {
"_id": "$sex",
"hobbies": { "$push": "$hobbies" },
"total": { "$sum": 1 }
}},
{ "$unwind": "$hobbies" },
{ "$unwind": "$hobbies" },
{ "$group": {
"_id": {
"sex": "$_id",
"hobby": "$hobbies"
},
"total": { "$first": "$total" },
"hobbyCount": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.sex",
"total": { "$first": "$total" },
"hobbies": {
"$push": { "name": "$_id.hobby", "count": "$hobbyCount" }
}
}}
])
Que produz um resultado como este:
[
{
"_id" : "female",
"total" : 1,
"hobbies" : [
{
"name" : "tennis",
"count" : 1
},
{
"name" : "football",
"count" : 1
}
]
},
{
"_id" : "male",
"total" : 2,
"hobbies" : [
{
"name" : "swimming",
"count" : 1
},
{
"name" : "tennis",
"count" : 2
},
{
"name" : "football",
"count" : 2
}
]
}
]
Portanto, o
$group
inicial faz a contagem por "sexo" e empilha os hobbies em uma matriz de matrizes. Então, para desnormalizá-lo $unwind
duas vezes para obter itens singulares, $group
para obter os totais por hobby em cada sexo e, finalmente, reagrupar uma matriz para cada sexo sozinho. São os mesmos dados, têm uma estrutura consistente e orgânica que é fácil de processar, e o MongoDB e a estrutura de agregação ficaram muito felizes em produzir essa saída.
Se você realmente precisa converter seus dados em nomes de chaves (e eu ainda recomendo que você não faça isso, pois não é um bom padrão a ser seguido no design), então fazer essa transformação do estado final é bastante trivial para o processamento do código do cliente. Como um exemplo básico de JavaScript adequado para o shell:
var out = db.people.aggregate([
{ "$group": {
"_id": "$sex",
"hobbies": { "$push": "$hobbies" },
"total": { "$sum": 1 }
}},
{ "$unwind": "$hobbies" },
{ "$unwind": "$hobbies" },
{ "$group": {
"_id": {
"sex": "$_id",
"hobby": "$hobbies"
},
"total": { "$first": "$total" },
"hobbyCount": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.sex",
"total": { "$first": "$total" },
"hobbies": {
"$push": { "name": "$_id.hobby", "count": "$hobbyCount" }
}
}}
]).toArray();
out.forEach(function(doc) {
var obj = {};
doc.hobbies.sort(function(a,b) { return a.count < b.count });
doc.hobbies.forEach(function(hobby) {
obj[hobby.name] = hobby.count;
});
doc.hobbies = obj;
printjson(doc);
});
E então você está basicamente processando cada resultado do cursor no formulário de saída desejado, que realmente não é uma função de agregação que é realmente necessária no servidor:
{
"_id" : "female",
"total" : 1,
"hobbies" : {
"tennis" : 1,
"football" : 1
}
}
{
"_id" : "male",
"total" : 2,
"hobbies" : {
"tennis" : 2,
"football" : 2,
"swimming" : 1
}
}
Onde isso também deve ser bastante trivial para implementar esse tipo de manipulação no processamento de fluxo do resultado do cursor para transformar conforme necessário, pois é basicamente a mesma lógica.
Por outro lado, você sempre pode implementar toda a manipulação no servidor usando mapReduce:
db.people.mapReduce(
function() {
emit(
this.sex,
{
"total": 1,
"hobbies": this.hobbies.map(function(key) {
return { "name": key, "count": 1 };
})
}
);
},
function(key,values) {
var obj = {},
reduced = {
"total": 0,
"hobbies": []
};
values.forEach(function(value) {
reduced.total += value.total;
value.hobbies.forEach(function(hobby) {
if ( !obj.hasOwnProperty(hobby.name) )
obj[hobby.name] = 0;
obj[hobby.name] += hobby.count;
});
});
reduced.hobbies = Object.keys(obj).map(function(key) {
return { "name": key, "count": obj[key] };
}).sort(function(a,b) {
return a.count < b.count;
});
return reduced;
},
{
"out": { "inline": 1 },
"finalize": function(key,value) {
var obj = {};
value.hobbies.forEach(function(hobby) {
obj[hobby.name] = hobby.count;
});
value.hobbies = obj;
return value;
}
}
)
Onde mapReduce tem seu próprio estilo distinto de saída, mas os mesmos princípios são usados na acumulação e manipulação, se não tão eficiente quanto a estrutura de agregação pode fazer:
"results" : [
{
"_id" : "female",
"value" : {
"total" : 1,
"hobbies" : {
"football" : 1,
"tennis" : 1
}
}
},
{
"_id" : "male",
"value" : {
"total" : 2,
"hobbies" : {
"football" : 2,
"tennis" : 2,
"swimming" : 1
}
}
}
]
No final das contas, ainda digo que a primeira forma de processamento é a mais eficiente e fornece, a meu ver, o trabalho mais natural e consistente da saída de dados, sem sequer tentar converter os pontos de dados em nomes de chaves. Provavelmente é melhor considerar seguir esse padrão, mas se você realmente precisa, existem maneiras de manipular os resultados em uma forma desejada em várias abordagens de processamento.