更新数组 mongodb 内的嵌套数组 [英] Updating nested array inside array mongodb

查看:31
本文介绍了更新数组 mongodb 内的嵌套数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下 mongodb 文档结构:

<预><代码>[{"_id": "04","name": "测试服务 4","id": "04",版本":0.0.1","title": "测试","描述": "测试",协议":测试",操作":[{"_id": "99","oName": "测试操作 52222222222","sid": "04","name": "测试操作 52222222222","oid": "99","描述": "测试",返回类型":测试",参数": [{"oName": "参数 1","name": "参数 1","pid": "011","类型": "582","描述": "测试",价值": ""},{"oName": "参数 2","name": "参数 2","pid": "012","类型": "58222","描述": "测试",价值": ""}]}]}]

我已经能够使用 $elemMatch 来更新操作中的字段,但是当我尝试对参数执行相同的操作(修改)时,它似乎不起作用.我想知道我应该尝试什么其他方法才能成功更新特定参数中的字段,通过其 pid 进行查找.

我目前拥有但不起作用的更新代码如下所示:

var oid = req.params.operations;var pid = req.params.parameters;collection.update({"parameters":{"$elemMatch": {"pid": pid}}},{"$set": {"parameters.$.name":req.body.name, "parameters.$.description": req.body.description,"parameters.$.oName": req.body.oName,"parameters.$.type": req.body.type} }, function(err, result) {如果(错误){console.log('更新服务出错:' + err);res.send({'error':'发生错误'});} 别的 {//console.log('' + result + '文档更新');res.send(结果);}});

解决方案

MongoDB 3.6 及更新版本

MongoDB 3.6 及以上版本提供了一项新功能,允许您使用 位置过滤的 $\[\] 语法,以匹配特定元素并通过 arrayFilters 在更新语句中:

const { oid, pid } = req.params;const { name, oName, description, type } = req.body;集合.更新({_id":1,操作":{$elemMatch":{oid, "parameters.pid": pid}}},{$set":{operations.$[outer].parameters.$[inner].name":名称,operations.$[outer].parameters.$[inner].description":描述,"operations.$[outer].parameters.$[inner].oName": oName,operations.$[outer].parameters.$[inner].type":类型} },{数组过滤器":[{ "outer.oid": oid },{ "inner.pid": pid }] }, (错误,结果) =>{如果(错误){console.log('更新服务出错:' + err);res.send({'error':'发生错误'});} 别的 {//console.log('' + result + '文档更新');res.send(结果);}});

<小时>

对于 MongoDB 3.4 及更早版本:

正如@wdberkeley 在评论中提到的:

<块引用>

MongoDB 不支持匹配到多个级别的数组.考虑改变你的文档模型,让每个文档代表一个操作,具有重复的一组操作的共同信息操作文档中.

我同意上述观点,并建议重新设计您的架构,因为 MongoDB 引擎不支持多个位置运算符(请参阅 多次使用位置$操作符更新嵌套数组)

但是,如果您事先知道具有要更新的参数对象的操作数组的索引,那么更新查询将是:

db.collection.update({"_id": "04","operations.parameters.pid": "011"},{$set":{"operations.0.parameters.$.name": "foo","operations.0.parameters.$.description": "bar","operations.0.parameters.$.type": "foo"}})

如果您想动态创建 $set 条件,即可以帮助您获取对象索引然后进行相应修改的东西,请考虑使用MapReduce.

目前这似乎无法使用聚合框架实现.有一个未解决的开放JIRA 问题 链接到它.但是,可以使用 MapReduce 来解决.MapReduce 的基本思想是它使用 JavaScript 作为其查询语言,但这往往比聚合框架慢得多,不应用于实时数据分析.

在您的 MapReduce 操作中,您需要定义几个步骤,即映射步骤(将操作映射到集合中的每个文档,该操作可以不做任何事情或发出一些带有键和投影值的对象)和减少步骤(获取发出的值列表并将其减少为单个元素).

对于映射步骤,理想情况下,您希望获取集合中的每个文档、每个 operations 数组字段的索引以及包含 $set 的另一个键键.

你的reduce步骤是一个函数(什么都不做),简单地定义为var reduce = function() {};

MapReduce 操作的最后一步将创建一个单独的集合操作,其中包含发出的操作数组对象以及具有 $set 条件的字段.当您对原始集合运行 MapReduce 操作时,可以定期更新此集合.总之,这个 MapReduce 方法看起来像:

var map = function(){for(var i = 0; i < this.operations.length; i++){发射({"_id": this._id,索引":我},{索引":我,操作":this.operations[i],更新": {名称":操作."+ i.toString() + ".parameters.$.name",描述":操作."+ i.toString() + ".parameters.$.description",类型":操作".+ i.toString() + ".parameters.$.type"}});}};var reduce = function(){};db.collection.mapReduce(地图,降低,{出去": {替换":操作"}});

从 MapReduce 操作查询输出集合 operations 通常会给你结果:

db.operations.findOne()

输出:

<代码>{_ID" : {"_id": "03",索引":0},价值" : {索引":0,操作":{"_id": "96","oName": "测试操作 52222222222","sid" : "04",名称":测试操作 52222222222","oid" : "99","描述": "测试",返回类型":测试",参数" : [{"oName": "参数 1","name" : "foo","pid" : "011",类型":富","描述": "酒吧",价值" : ""},{"oName": "参数 2","name": "参数 2","pid" : "012",类型":58222","描述": "测试",价值" : ""}]},更新" : {"name" : "operations.0.parameters.$.name","description" : "operations.0.parameters.$.description","type" : "operations.0.parameters.$.type"}}}

然后您可以使用 db.operations.find() 方法中的游标来迭代并相应地更新您的集合:

var oid = req.params.operations;var pid = req.params.parameters;var cur = db.operations.find({"_id._id": oid, "value.operations.parameters.pid": pid });//迭代结果并使用数组索引语法动态更新查询对象集进行更新.而(cur.hasNext()){var doc = cur.next();var update = { "$set": {} };//设置更新查询对象更新["$set"][doc.value.update.name] = req.body.name;更新[$set"][doc.value.update.description] = req.body.description;更新["$set"][doc.value.update.type] = req.body.type;db.collection.update({"_id" : oid,"operations.parameters.pid": pid},更新);};

I have the following mongodb document structure:

[
    {
        "_id": "04",
        "name": "test service 4",
        "id": "04",
        "version": "0.0.1",
        "title": "testing",
        "description": "test",
        "protocol": "test",
        "operations": [
            {
                "_id": "99",
                "oName": "test op 52222222222",
                "sid": "04",
                "name": "test op 52222222222",
                "oid": "99",
                "description": "testing",
                "returntype": "test",
                "parameters": [
                    {
                        "oName": "Param1",
                        "name": "Param1",
                        "pid": "011",
                        "type": "582",
                        "description": "testing",
                        "value": ""
                    },
                    {
                        "oName": "Param2",
                        "name": "Param2",
                        "pid": "012",
                        "type": "58222",
                        "description": "testing",
                        "value": ""
                    }
                ]
            }
        ]
    }
]

I have been able to use $elemMatch in order to update fields in operations, but when I try to do the same thing (modified) for parameters it does not seem to work. I was wondering what other approach should I look into trying in order to be able to successfully update fields in a specific parameter, looking it up by its pid.

The update code I currently have and does not work looks like this:

var oid = req.params.operations;
var pid = req.params.parameters;

collection.update({"parameters":{"$elemMatch": {"pid": pid}}},{"$set": {"parameters.$.name":req.body.name, "parameters.$.description": req.body.description,"parameters.$.oName": req.body.oName,"parameters.$.type": req.body.type} }, function(err, result) {
        if (err) {
            console.log('Error updating service: ' + err);
            res.send({'error':'An error has occurred'});
        } else {
            // console.log('' + result + ' document(s) updated');
            res.send(result);
        }
    });

解决方案

MongoDB 3.6 and newer

With MongoDB 3.6 and above comes a new feature that allows you to update nested arrays by using the positional filtered $\[<identifier>\] syntax in order to match the specific elements and apply different conditions through arrayFilters in the update statement:

const { oid, pid } = req.params;
const { name, oName, description, type } = req.body; 

collection.update(
    {
        "_id": 1,
        "operations": {
            "$elemMatch": {
                oid, "parameters.pid": pid
            }
        }
    },
    { "$set": { 
        "operations.$[outer].parameters.$[inner].name": name,
        "operations.$[outer].parameters.$[inner].description": description,
        "operations.$[outer].parameters.$[inner].oName": oName,
        "operations.$[outer].parameters.$[inner].type": type 
    } },
    { "arrayFilters": [
        { "outer.oid": oid },
        { "inner.pid": pid }
    ] }, (err, result) => {
    if (err) {
        console.log('Error updating service: ' + err);
        res.send({'error':'An error has occurred'});
    } else {
        // console.log('' + result + ' document(s) updated');
        res.send(result);
    }
});


For MongoDB 3.4 and older:

As @wdberkeley mentioned in his comment:

MongoDB doesn't support matching into more than one level of an array. Consider altering your document model so each document represents an operation, with information common to a set of operations duplicated in the operation documents.

I concur with the above and would recommend redesigning your schema as MongoDB engine does not support multiple positional operators ( See Multiple use of the positional $ operator to update nested arrays)

However, if you know the index of the operations array that has the parameters object to be updated beforehand then the update query will be:

db.collection.update(
    {
        "_id" : "04", 
        "operations.parameters.pid": "011"
    }, 
    {
        "$set": { 
            "operations.0.parameters.$.name": "foo",
            "operations.0.parameters.$.description": "bar", 
            "operations.0.parameters.$.type": "foo" 
        }
    }
)

EDIT:

If you would like to create the $set conditions on the fly i.e. something which would help you get the indexes for the objects and then modify accordingly, then consider using MapReduce.

Currently this seems to be not possible using the aggregation framework. There is an unresolved open JIRA issue linked to it. However, a workaround is possible with MapReduce. The basic idea with MapReduce is that it uses JavaScript as its query language but this tends to be fairly slower than the aggregation framework and should not be used for real-time data analysis.

In your MapReduce operation, you need to define a couple of steps i.e. the mapping step (which maps an operation into every document in the collection, and the operation can either do nothing or emit some object with keys and projected values) and reducing step (which takes the list of emitted values and reduces it to a single element).

For the map step, you ideally would want to get for every document in the collection, the index for each operations array field and another key that contains the $set keys.

Your reduce step would be a function (which does nothing) simply defined as var reduce = function() {};

The final step in your MapReduce operation will then create a separate collection operations that contains the emitted operations array object along with a field with the $set conditions. This collection can be updated periodically when you run the MapReduce operation on the original collection. Altogether, this MapReduce method would look like:

var map = function(){
    for(var i = 0; i < this.operations.length; i++){
        emit( 
            {
                "_id": this._id, 
                "index": i 
            }, 
            {
                "index": i, 
                "operations": this.operations[i],            
                "update": {
                    "name": "operations." + i.toString() + ".parameters.$.name",
                    "description": "operations." + i.toString() + ".parameters.$.description",
                    "type": "operations." + i.toString() + ".parameters.$.type"
                }                    
            }
        );
    }
};

var reduce = function(){};

db.collection.mapReduce(
    map,
    reduce,
    {
        "out": {
            "replace": "operations"
        }
    }
);

Querying the output collection operations from the MapReduce operation will typically give you the result:

db.operations.findOne()

Output:

{
    "_id" : {
        "_id" : "03",
        "index" : 0
    },
    "value" : {
        "index" : 0,
        "operations" : {
            "_id" : "96",
            "oName" : "test op 52222222222",
            "sid" : "04",
            "name" : "test op 52222222222",
            "oid" : "99",
            "description" : "testing",
            "returntype" : "test",
            "parameters" : [ 
                {
                    "oName" : "Param1",
                    "name" : "foo",
                    "pid" : "011",
                    "type" : "foo",
                    "description" : "bar",
                    "value" : ""
                }, 
                {
                    "oName" : "Param2",
                    "name" : "Param2",
                    "pid" : "012",
                    "type" : "58222",
                    "description" : "testing",
                    "value" : ""
                }
            ]
        },
        "update" : {
            "name" : "operations.0.parameters.$.name",
            "description" : "operations.0.parameters.$.description",
            "type" : "operations.0.parameters.$.type"
        }
    }
}

You can then use the cursor from the db.operations.find() method to iterate over and update your collection accordingly:

var oid = req.params.operations;
var pid = req.params.parameters;
var cur = db.operations.find({"_id._id": oid, "value.operations.parameters.pid": pid });

// Iterate through results and update using the update query object set dynamically by using the array-index syntax.
while (cur.hasNext()) {
    var doc = cur.next();
    var update = { "$set": {} };
    // set the update query object
    update["$set"][doc.value.update.name] = req.body.name;
    update["$set"][doc.value.update.description] = req.body.description;
    update["$set"][doc.value.update.type] = req.body.type;

    db.collection.update(
        {
            "_id" : oid, 
            "operations.parameters.pid": pid
        }, 
        update 
    );
};

这篇关于更新数组 mongodb 内的嵌套数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆