Secara pribadi saya bukan penggemar berat mengubah "data" sebagai nama kunci sebagai hasilnya. Prinsip kerangka kerja agregasi cenderung setuju karena operasi semacam ini juga tidak didukung.
Jadi preferensi pribadi adalah untuk mempertahankan "data" sebagai "data" dan menerima bahwa output yang diproses sebenarnya lebih baik dan lebih logis untuk desain objek yang konsisten:
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" }
}
}}
])
Yang menghasilkan hasil seperti ini:
[
{
"_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
}
]
}
]
Jadi inisial $group
melakukan hitungan per "jenis kelamin" dan menumpuk hobi ke dalam array array. Kemudian untuk menormalkan Anda $unwind
dua kali untuk mendapatkan item tunggal, $group
untuk mendapatkan total per hobi di bawah setiap jenis kelamin dan akhirnya mengelompokkan kembali array untuk setiap jenis kelamin saja.
Ini adalah data yang sama, memiliki struktur yang konsisten dan organik yang mudah diproses, dan MongoDB serta kerangka kerja agregasi cukup senang dalam menghasilkan keluaran ini.
Jika Anda benar-benar harus mengonversi data Anda menjadi nama kunci (dan saya tetap menyarankan Anda untuk tidak melakukannya karena ini bukan pola yang baik untuk diikuti dalam desain), maka melakukan transformasi seperti itu dari status akhir cukup sepele untuk pemrosesan kode klien. Sebagai contoh JavaScript dasar yang cocok untuk 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);
});
Dan kemudian Anda pada dasarnya memproses setiap hasil kursor ke dalam bentuk output yang diinginkan, yang sebenarnya bukanlah fungsi agregasi yang benar-benar diperlukan di server:
{
"_id" : "female",
"total" : 1,
"hobbies" : {
"tennis" : 1,
"football" : 1
}
}
{
"_id" : "male",
"total" : 2,
"hobbies" : {
"tennis" : 2,
"football" : 2,
"swimming" : 1
}
}
Di mana itu juga harus cukup sepele untuk menerapkan manipulasi semacam itu ke dalam pemrosesan aliran hasil kursor untuk diubah sesuai kebutuhan, karena pada dasarnya hanya logika yang sama.
Di sisi lain, Anda selalu dapat menerapkan semua manipulasi di server menggunakan mapReduce sebagai gantinya:
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;
}
}
)
Di mana mapReduce memiliki gaya keluarannya sendiri yang berbeda, tetapi prinsip yang sama digunakan dalam akumulasi dan manipulasi, jika tidak seefisien yang dapat dilakukan oleh kerangka kerja agregasi:
"results" : [
{
"_id" : "female",
"value" : {
"total" : 1,
"hobbies" : {
"football" : 1,
"tennis" : 1
}
}
},
{
"_id" : "male",
"value" : {
"total" : 2,
"hobbies" : {
"football" : 2,
"tennis" : 2,
"swimming" : 1
}
}
}
]
Pada akhirnya, saya masih mengatakan bahwa bentuk pemrosesan pertama adalah yang paling efisien dan memberikan pikiran saya kerja output data yang paling alami dan konsisten, bahkan tanpa berusaha mengubah titik data menjadi nama kunci. Mungkin lebih baik untuk mempertimbangkan mengikuti pola itu, tetapi jika Anda benar-benar harus melakukannya, maka ada cara untuk memanipulasi hasil menjadi bentuk yang diinginkan dalam berbagai pendekatan pemrosesan.