Osobiście nie jestem wielkim fanem przekształcania „danych” jako w rezultacie nazw kluczy. Zasady ram agregacji zwykle się zgadzają, ponieważ ten rodzaj operacji również nie jest obsługiwany.
Tak więc osobistą preferencją jest zachowanie „danych” jako „danych” i zaakceptowanie, że przetworzone dane wyjściowe są faktycznie lepsze i bardziej logiczne dla spójnego projektu obiektu:
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" }
}
}}
])
Co daje taki wynik:
[
{
"_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
}
]
}
]
Więc początkowe $group
liczy na „płeć” i układa hobby w tablice. Następnie, aby dokonać denormalizacji $unwind
dwa razy, aby uzyskać pojedyncze elementy, $group
aby uzyskać sumy na hobby dla każdej płci i ostatecznie przegrupować tablicę dla każdej płci osobno.
To te same dane, mają spójną i organiczną strukturę, która jest łatwa do przetworzenia, a MongoDB i struktura agregacji były całkiem zadowolone z wygenerowania tego wyniku.
Jeśli naprawdę musisz przekonwertować swoje dane na nazwy kluczy (a nadal nie polecam, ponieważ nie jest to dobry wzorzec do naśladowania w projektowaniu ), to wykonanie takiej transformacji ze stanu końcowego jest dość trywialne dla przetwarzania kodu klienta. Jako podstawowy przykład JavaScript odpowiedni dla powłoki:
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);
});
A potem w zasadzie przetwarzasz każdy wynik kursora w żądany formularz wyjściowy, który tak naprawdę nie jest funkcją agregującą, która i tak jest naprawdę wymagana na serwerze:
{
"_id" : "female",
"total" : 1,
"hobbies" : {
"tennis" : 1,
"football" : 1
}
}
{
"_id" : "male",
"total" : 2,
"hobbies" : {
"tennis" : 2,
"football" : 2,
"swimming" : 1
}
}
Tam, gdzie powinno to być również dość trywialne, aby zaimplementować ten rodzaj manipulacji w przetwarzaniu strumieniowym wyniku kursora, aby zmienić się zgodnie z wymaganiami, ponieważ jest to w zasadzie ta sama logika.
Z drugiej strony, zawsze możesz zaimplementować całą manipulację na serwerze za pomocą 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 ma swój własny, odrębny styl wyników, ale te same zasady są używane w akumulacji i manipulacji, jeśli nie jest to prawdopodobnie tak wydajne, jak może to zrobić struktura agregacji:
"results" : [
{
"_id" : "female",
"value" : {
"total" : 1,
"hobbies" : {
"football" : 1,
"tennis" : 1
}
}
},
{
"_id" : "male",
"value" : {
"total" : 2,
"hobbies" : {
"football" : 2,
"tennis" : 2,
"swimming" : 1
}
}
}
]
Pod koniec dnia nadal mówię, że pierwsza forma przetwarzania jest najbardziej wydajna i zapewnia moim zdaniem najbardziej naturalne i spójne działanie danych wyjściowych, nawet bez próby zamiany punktów danych na nazwy kluczy. Prawdopodobnie najlepiej jest rozważyć podążanie za tym wzorcem, ale jeśli naprawdę musisz, istnieją sposoby manipulowania wynikami w żądaną formę w różnych podejściach do przetwarzania.