Persoonlijk ben ik geen grote fan van het transformeren van "gegevens" als namen van sleutels in een resultaat. De principes van het aggregatieraamwerk komen vaak overeen, aangezien dit soort bewerkingen ook niet worden ondersteund.
Dus de persoonlijke voorkeur is om "data" als "data" te behouden en te accepteren dat de verwerkte uitvoer eigenlijk beter en logischer is voor een consistent objectontwerp:
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" }
}
}}
])
Wat een resultaat als dit oplevert:
[
{
"_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
}
]
}
]
Dus de eerste $group
telt per "geslacht" en stapelt de hobby's op in een reeks arrays. Om je vervolgens te de-normaliseren $unwind
twee keer om unieke items te krijgen, $group
om de totalen per hobby onder elk geslacht te krijgen en uiteindelijk een array voor elk geslacht alleen te hergroeperen.
Het zijn dezelfde gegevens, het heeft een consistente en organische structuur die gemakkelijk te verwerken is, en MongoDB en het aggregatieraamwerk waren best tevreden met het produceren van deze uitvoer.
Als je je gegevens echt moet converteren naar namen van sleutels (en ik raad je nog steeds aan om dat niet te doen, omdat het geen goed patroon is om te volgen in het ontwerp), dan is het doen van een dergelijke transformatie vanuit de uiteindelijke staat vrij triviaal voor de verwerking van clientcodes. Als een eenvoudig JavaScript-voorbeeld dat geschikt is voor de 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);
});
En dan verwerkt u in feite elk cursorresultaat in de gewenste uitvoervorm, wat eigenlijk geen aggregatiefunctie is die hoe dan ook echt nodig is op de server:
{
"_id" : "female",
"total" : 1,
"hobbies" : {
"tennis" : 1,
"football" : 1
}
}
{
"_id" : "male",
"total" : 2,
"hobbies" : {
"tennis" : 2,
"football" : 2,
"swimming" : 1
}
}
Waar dat ook redelijk triviaal zou moeten zijn om dat soort manipulatie te implementeren in streamverwerking van het cursorresultaat om te transformeren zoals vereist, omdat het in feite gewoon dezelfde logica is.
Aan de andere kant kunt u in plaats daarvan altijd alle manipulatie op de server implementeren met 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;
}
}
)
Waar mapReduce zijn eigen specifieke uitvoerstijl heeft, maar dezelfde principes worden gebruikt bij accumulatie en manipulatie, misschien niet zo efficiënt als het aggregatieraamwerk kan doen:
"results" : [
{
"_id" : "female",
"value" : {
"total" : 1,
"hobbies" : {
"football" : 1,
"tennis" : 1
}
}
},
{
"_id" : "male",
"value" : {
"total" : 2,
"hobbies" : {
"football" : 2,
"tennis" : 2,
"swimming" : 1
}
}
}
]
Aan het eind van de dag zeg ik nog steeds dat de eerste vorm van verwerking de meest efficiënte is en naar mijn mening de meest natuurlijke en consistente werking van de gegevensuitvoer biedt, zonder zelfs maar te proberen de gegevenspunten om te zetten in de namen van sleutels. Het is waarschijnlijk het beste om te overwegen dat patroon te volgen, maar als het echt moet, dan zijn er manieren om resultaten in een gewenste vorm te manipuleren in verschillende benaderingen van verwerking.