Лично аз не съм голям фен на преобразуването на "данни" като имена на ключове в резултат. Принципите на рамката за агрегиране са склонни да се съгласуват, тъй като този вид операции също не се поддържат.
Така че личното предпочитание е да поддържате „данните“ като „данни“ и да приемете, че обработеният изход всъщност е по-добър и по-логичен за последователен обектен дизайн:
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" }
}
}}
])
Което дава резултат като този:
[
{
"_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
}
]
}
]
Така че първоначалната $group
отчита се на "пол" и подрежда хобитата в масив от масиви. След това да ви денормализира $unwind
два пъти, за да получите единични елементи, $group
за да получите общите суми за хоби за всеки пол и накрая да прегрупирате масив за всеки пол самостоятелно.
Това са едни и същи данни, те имат последователна и органична структура, която е лесна за обработка, а MongoDB и рамката за агрегиране бяха доста доволни от производството на този изход.
Ако наистина трябва да преобразувате данните си в имена на ключове (и все пак ви препоръчвам да не го правите, тъй като не е добър модел, който да следвате в дизайна), тогава извършването на такава трансформация от крайното състояние е доста тривиално за обработка на клиентски код. Като основен пример за JavaScript, подходящ за обвивката:
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);
});
И тогава вие основно обработвате всеки резултат от курсора в желаната изходна форма, която наистина не е функция за агрегиране, която така или иначе наистина се изисква на сървъра:
{
"_id" : "female",
"total" : 1,
"hobbies" : {
"tennis" : 1,
"football" : 1
}
}
{
"_id" : "male",
"total" : 2,
"hobbies" : {
"tennis" : 2,
"football" : 2,
"swimming" : 1
}
}
Където това също би трябвало да е доста несериозно, за да се приложи такъв вид манипулация в обработката на потока на резултата от курсора, за да се трансформира според изискванията, тъй като по същество това е същата логика.
От друга страна, винаги можете да приложите цялата манипулация на сървъра, като използвате 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;
}
}
)
Където mapReduce има свой собствен различен стил на извеждане, но същите принципи се използват при натрупване и манипулиране, ако не е толкова ефективно, колкото може да направи рамката за агрегиране:
"results" : [
{
"_id" : "female",
"value" : {
"total" : 1,
"hobbies" : {
"football" : 1,
"tennis" : 1
}
}
},
{
"_id" : "male",
"value" : {
"total" : 2,
"hobbies" : {
"football" : 2,
"tennis" : 2,
"swimming" : 1
}
}
}
]
В крайна сметка все още казвам, че първата форма на обработка е най-ефективната и осигурява според мен най-естествената и последователна работа на изходните данни, без дори да се опитвам да преобразувам точките от данни в имената на ключовете. Вероятно е най-добре да обмислите да следвате този модел, но ако наистина трябва, тогава има начини за манипулиране на резултатите в желаната форма в различни подходи за обработка.