如何将MongoDB中的数组转换为单个标量值?

时间:2015-12-09 15:36:40

标签: mongodb mapreduce mongodb-query aggregation-framework

我有一个MongoDB文档,看起来有几个数组属性:

{
    "_id" : "123456789",
    "distance" : [ 
        {
            "inner_distance" : 2
        },
        {
            "inner_distance" : 4
        },
        {
            "inner_distance" : -1
        }
    ],
    "name" : [ 
        {
            "inner_name" : "MyName"
        }
    ],
    "entries" : [ 
        { ... }, 
        { ... }, 
    ],
    "property1" : "myproperty1",
    "property2" : "myproperty2",
    "property3" : "myproperty3"
}

我试图弄清楚如何将转换应用于distance数组,以便“展平”#34;根据转换函数将其转换为标量(我想获取距离中每个inner_distance元素的绝对值,然后取所有这些值的最小值。)

例如,在上面的示例中,distance数组包含:[{"inner_distance" : 2}, {"inner_distance" : 4}, {"inner_distance" : -1}],我需要弄清楚如何应用转换来制作distance: 1(或者如果它更容易,新属性,例如distance_new: 1

我想内联执行此操作(是正确的术语吗?),以便我执行操作并以存储的记录结束:

{
    "_id" : "123456789",
    "distance" : 1,
    "name" : [ 
        {
            "inner_name" : "MyName"
        }
    ],
    "entries" : [ 
        { ... }, 
        { ... }, 
    ],
    "property1" : "myproperty1",
    "property2" : "myproperty2",
    "property3" : "myproperty3"
}

有没有人对这样的事情有任何经验?我一直在试图弄清楚如何创建一个map-reduce命令来运行它但没有运气。

1 个答案:

答案 0 :(得分:2)

你想要的东西可以在MongoDB 3.2中有效处理。

您需要使用$abs运算符返回每个" inner_distance"的绝对值。以及$min返回数组中的最小值。当然,$map阶段中的$project运算符会返回" inner_distance"的数组。

然后,您需要循环聚合结果并使用.bulkWrite()方法更新文档。

var operations = [];
db.collection.aggregate([
    { "$project": { 
        "distance": { 
            "$min": { 
                "$map": { 
                    "input": "$distance", 
                    "as": "d", 
                    "in": { "$abs": "$$d.inner_distance" }
                }
            }
        }
    }}
]).forEach(function(doc) {
    var operation = { 'updateOne': { 
        'filter': { '_id': doc._id }, 
        'update': { 
            '$set': { 'distance': doc.distance }
        }
    }};
    operations.push(operation); 
});
operations.push( {
    ordered: true,      
    writeConcern: { w: "majority", wtimeout: 5000 } 
});

db.collection.bulkWrite(operations);

mapReduce解决方案

var map = function() { 
    var distance = this.distance.map(function(element) { 
        return Math.abs(element.inner_distance); 
    } ); 
    emit(this._id, Math.min(...distance)); 
};

var results =  db.collection.mapReduce(map, 
    function(key, values) { return;}, 
    { 'out': { 'inline': 1 } }
);

返回此内容:

{
        "results" : [
                {
                        "_id" : "123456789",
                        "value" : 1
                },
                {
                        "_id" : "143456789",
                        "value" : 1
                }
        ],
        "timeMillis" : 31,
        "counts" : {
                "input" : 2,
                "emit" : 2,
                "reduce" : 0,
                "output" : 2
        },
        "ok" : 1
}

然后,您可以使用"bulk"操作来更新文档。

var bulk = db.collection.initializeOrderedBulkOp();
var count = 0;
results['results'].forEach(function(element) {
    bulk.find( { '_id': element._id } ).updateOne( {
        '$set': { 'distance': element.value }
    });
    count++;
    if (count % 200 === 0) {
        bulk.execute();
        bulk = db.collection.initializeOrderedBulkOp();
    }
})

if (count > 0 )  bulk.execute();

注意:

在mapReduce示例中,Math.min(...distance)使用ES6中的spread operator新内容,但您也可以使用Math.min.apply(Math, distance)