個人的には、結果のキーの名前として「データ」を変換することはあまり好きではありません。この種の操作もサポートされていないため、集約フレームワークの原則は一致する傾向があります。
したがって、個人的な好みは、「データ」を「データ」として維持し、処理された出力が実際には一貫したオブジェクト設計に対してより適切で論理的であることを受け入れることです。
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
単一のアイテムを取得するには2回、$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
}
}
}
]
結局のところ、最初の形式の処理が最も効率的であり、データポイントをキーの名前に変換しようとせずに、データ出力の最も自然で一貫した動作を心に留めていると私は言います。そのパターンに従うことを検討するのがおそらく最善ですが、本当に必要な場合は、処理へのさまざまなアプローチで結果を目的の形式に操作する方法があります。