一尘不染

更新数组mongodb中的嵌套数组

node.js

我有以下mongodb文件结构:

[
    {
        "_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": ""
                    }
                ]
            }
        ]
    }
]

我已经能够使用$
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) {
        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);
        }
    });

阅读 322

收藏
2020-07-07

共1个答案

一尘不染

MongoDB 3.6及更高版本

MongoDB3.6及更高版本提供了一项新功能,该功能允许您使用位置过滤的$\[<identifier>\]语法来更新嵌套数组,以匹配特定元素并arrayFilters在update语句中应用不同的条件:

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);
    }
});

对于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++){
        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"
        }
    }
);

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

db.operations.findOne()

输出

{
    "_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"
        }
    }
}

然后,您可以使用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 });

// 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 
    );
};
2020-07-07