MongoDBでこれを解決するには、2つのアプローチがあります。
サブセットがかなり小さい場合は、サブセットに対してクエリを実行してすべてのメンバーを検索し、そのクエリの結果をmap-reduce呼び出しの最初のクエリとして使用できます。
ただし、サブセットが非常に大きい場合、これは不可能な場合があります。次にできることは、「reduce」出力オプションを使用して2つのmap-reduce呼び出しを使用して結合をシミュレートし、同じターゲットコレクションに縮小することです。これにより、ドキュメントが次のようになる中間コレクションが作成されます。
{Name: Jim, Age: 24, inSubset: true}
{Name: Bill, Age: 38, inSubset: false}
{Name: Mary, Age: 55, inSubset: true}
最後に、この中間コレクションで3番目のマップリデュースを実行して、を含むすべてのドキュメントを平均することができますinSubset: true
。
pymongoドライバーを使用したPythonの2.オプション(3つのmap-reduces)のコードは次のとおりです。
from pymongo import Connection
from bson import ObjectId, Code
con = Connection(port=30000) # add host/port here if different from default
db = con['test'] # or the database name you are using
# insert documents
db.master.insert({'_id': ObjectId(), 'Name': 'Jim', 'Age': 24})
db.master.insert({'_id': ObjectId(), 'Name': 'Bill', 'Age': 38})
db.master.insert({'_id': ObjectId(), 'Name': 'Mary', 'Age': 55})
db.subset.insert({'_id': ObjectId(), 'Name': 'Jim'})
db.subset.insert({'_id': ObjectId(), 'Name': 'Mary'})
# map function for master collection
mapf_master = Code(""" function () {
emit(this.Name, {'age': this.Age, 'inSubset': false});
} """)
# map function for subset collection
mapf_subset = Code(""" function() {
emit(this.Name, {'age': 0, 'inSubset': true});
} """)
# reduce function for both master and subset
reducef = Code(""" function(key, values) {
var result = {'age': 0, 'inSubset': false};
values.forEach( function(value) {
result.age += value.age;
result.inSubset = result.inSubset || value.inSubset;
});
return result;
} """)
# call map-reduce on master and subset (simulates a join)
db.master.map_reduce(mapf_master, reducef, out={'reduce': 'join'})
db.subset.map_reduce(mapf_subset, reducef, out={'reduce': 'join'})
# final map function for third map-reduce call
mapf_final = Code(""" function() {
if (this.value.inSubset) {
emit('total', {'age': this.value.age, 'count': 1});
}
} """)
# final reduce function for third map-reduce call
reducef_final = Code(""" function(key, values) {
var result = {'age': 0, 'count': 0};
values.forEach( function(value) {
result.age += value.age;
result.count += value.count;
});
return result;
} """)
# final finalize function, calculates the average
finalizef_final = Code(""" function(key, value) {
if (value.count > 0) {
value.averageAge = value.age / value.count;
}
return value;
} """)
# call final map-reduce
db.join.map_reduce(mapf_final, reducef_final, finalize=finalizef_final, out={'merge': 'result'})
結果のコレクションは次のようになります(mongoシェルから照会):
> db.result.find()
{ "_id" : "total", "value" : { "age" : 79, "count" : 2, "averageAge" : 39.5 } }
最終的な平均はvalue.averageAgeフィールドに保存されます。