过滤器映射会缩减为与不同集合匹配的记录

时间:2012-09-13 23:51:55

标签: python mongodb mapreduce

我试图找出一种在mongo / python中执行以下操作的优雅方法:我有两个集合:一个包含人员和属性列表,另一个包含人口子集“人口子集”。我想运行一个map reduce工作来计算大型列表中的一些聚合统计数据,但只使用人口样本中出现的人员名称。以下是一组示例记录:

master_list: [{ Name: Jim }, { Age: 24}
              { Name: Bill}, { Age: 38}
              { Name: Mary}, { Age: 55}]

subset : [{ Name: Jim}
          { Name: Mary}]

这个想法是计算年龄的平均值,但只使用master_list中的三个记录中的两个,如子集中所列。我知道mongo中的map_reduce支持查询参数,但不清楚处理上面的最佳方法是什么,没有加入。一个选项是我预处理master_list并创建一个属性'include'来标记要使用的记录,然后在map_reduce过滤器中对其进行操作。虽然看起来像kludgy并且在我的数据库中创建了一个永久性标志,这种标志因各种原因而烦人。

更新

在阅读了查询中嵌入列表的建议之后,我能够通过以下内容得到我需要的内容

map_reduce(mapper, reducer, out = {'merge': 'Stats'}, 
           finalize = finalizer, scope = {'atts': f},
           query = {'Name' : { '$in' : pop }})

其中pop是一个python名称列表。谢谢!

1 个答案:

答案 0 :(得分:4)

在MongoDB中有两种方法可以解决这个问题。

  1. 如果您的子集相当小,您只需对子集进行查询即可查找所有成员,并将该查询的结果用作map-reduce调用的初始query

  2. 但是,如果您有非常大的子集,则可能无法实现。那么你可以做的是使用'reduce'输出选项将simulate a join using two map-reduce calls缩减为相同的目标集合。这将创建一个中间集合,其中的文档如下所示:

    {Name: Jim, Age: 24, inSubset: true}
    {Name: Bill, Age: 38, inSubset: false}
    {Name: Mary, Age: 55, inSubset: true}
    

    最后,您可以对此中间集合执行第三个map reduce,以平均所有具有inSubset: true的文档。

  3. 以下是Python中使用pymongo驱动程序的2.选项(三个map-reduced)的代码:

    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 shell查询):

    > db.result.find()
    { "_id" : "total", "value" : { "age" : 79, "count" : 2, "averageAge" : 39.5 } }
    

    ,最终平均值存储在value.averageAge字段中。