Ich persönlich bin kein großer Fan davon, "Daten" als Namen von Schlüsseln in ein Ergebnis umzuwandeln. Die Prinzipien des Aggregations-Frameworks stimmen tendenziell überein, da diese Art von Vorgang ebenfalls nicht unterstützt wird.
Die persönliche Präferenz besteht also darin, "Daten" als "Daten" beizubehalten und zu akzeptieren, dass die verarbeitete Ausgabe tatsächlich besser und logischer für ein konsistentes Objektdesign ist:
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" }
}
}}
])
Was zu einem Ergebnis wie diesem führt:
[
{
"_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
}
]
}
]
Also die anfängliche $group
zählt pro "Geschlecht" und stapelt die Hobbys in einer Reihe von Arrays. Um Sie dann $unwind
zu denormalisieren zweimal, um einzelne Gegenstände zu erhalten, $group
um die Gesamtsummen pro Hobby unter jedem Geschlecht zu erhalten und schließlich ein Array für jedes Geschlecht allein neu zu gruppieren.
Es sind die gleichen Daten, sie haben eine konsistente und organische Struktur, die leicht zu verarbeiten ist, und MongoDB und das Aggregations-Framework waren mit der Erstellung dieser Ausgabe sehr zufrieden.
Wenn Sie Ihre Daten wirklich in Namen von Schlüsseln konvertieren müssen ( und ich empfehle Ihnen immer noch, dies nicht zu tun, da es kein gutes Muster ist, dem Sie beim Design folgen können), dann ist eine solche Transformation aus dem endgültigen Zustand für die Client-Code-Verarbeitung ziemlich trivial. Als einfaches, für die Shell geeignetes JavaScript-Beispiel:
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);
});
Und dann verarbeiten Sie im Grunde jedes Cursor-Ergebnis in die gewünschte Ausgabeform, was wirklich keine Aggregationsfunktion ist, die auf dem Server sowieso wirklich benötigt wird:
{
"_id" : "female",
"total" : 1,
"hobbies" : {
"tennis" : 1,
"football" : 1
}
}
{
"_id" : "male",
"total" : 2,
"hobbies" : {
"tennis" : 2,
"football" : 2,
"swimming" : 1
}
}
Wobei es auch ziemlich trivial sein sollte, diese Art von Manipulation in die Stream-Verarbeitung des Cursor-Ergebnisses zu implementieren, um es nach Bedarf zu transformieren, da es im Grunde genau die gleiche Logik ist.
Auf der anderen Seite können Sie stattdessen immer alle Manipulationen auf dem Server mit mapReduce implementieren:
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;
}
}
)
Wobei mapReduce seinen eigenen Ausgabestil hat, aber die gleichen Prinzipien bei der Akkumulation und Manipulation verwendet werden, wenn auch nicht so effizient wie das Aggregations-Framework:
"results" : [
{
"_id" : "female",
"value" : {
"total" : 1,
"hobbies" : {
"football" : 1,
"tennis" : 1
}
}
},
{
"_id" : "male",
"value" : {
"total" : 2,
"hobbies" : {
"football" : 2,
"tennis" : 2,
"swimming" : 1
}
}
}
]
Am Ende des Tages sage ich immer noch, dass die erste Form der Verarbeitung die effizienteste ist und meiner Meinung nach das natürlichste und konsistenteste Arbeiten der Datenausgabe bietet, ohne auch nur zu versuchen, die Datenpunkte in die Namen von Schlüsseln umzuwandeln. Es ist wahrscheinlich am besten, diesem Muster zu folgen, aber wenn Sie wirklich müssen, dann gibt es Möglichkeiten, Ergebnisse in einer gewünschten Form in verschiedenen Ansätzen zur Verarbeitung zu manipulieren.