更新数组mongodb中的嵌套数组

时间:2015-04-14 17:50:53

标签: arrays node.js mongodb api

我有以下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);
        }
    });

4 个答案:

答案 0 :(得分:9)

MongoDB 3.6及更新版

使用MongoDB 3.6及更高版本提供了一项新功能,允许您使用the positional filtered $\[<identifier>\]语法更新嵌套数组,以便匹配特定元素并通过更新语句中的arrayFilters应用不同条件:< / p>

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引擎不支持多个位置运算符(参见 Multiple use of the positional $ operator to update nested arrays

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

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 条件,即可以帮助您获取对象索引然后相应修改的内容,请考虑使用 { {3}}

目前,使用聚合框架似乎无法做到这一点。有一个未解决的开放 MapReduce 与之相关联。但是, JIRA issue 可以解决方法。 MapReduce的基本思想是它使用JavaScript作为其查询语言,但这往往比聚合框架慢得多,不应该用于实时数据分析。

在MapReduce操作中,您需要定义几个步骤,即映射步骤(将操作映射到集合中的每个文档,操作可以不执行任何操作或使用键和投影值发出一些对象)和减少步骤(采用发射值列表并将其减少为单个元素)。

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

您的简化步骤将是一个函数(无效),简单定义为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"
        }
    }
);

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

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

答案 1 :(得分:2)

如果是经常更改的数据,则应该展平结构并将大量变化的数据与不变化的数据分开。

如果数据不经常更改,并且整个数据对象不是很大,只需修改对象客户端,然后更新整个对象。

答案 2 :(得分:1)

我们将尝试查找外部数组(i)和内部数组(j)的索引,然后更新

collection.findById(04)
.then(result =>{
    for(let i = 0; i<result.operations.length; i++){
        if(result.operation[i]._id == "99"){
            let parameters = result.operations[i].parameters;`enter code here`
            for(let j = 0; j<parameters.length; j++){
                if(parameters[j].pid == "011"){
                    console.log("i", i);
                    console.log("j", j);
                    let data = {}
                    data["operations." + i + ".parameters." + j + ".oName"] = updateoName
                    data["operations." + i + ".parameters." + j + ".name"] = updatename
                    data["operations." + i + ".parameters." + j + ".pid"] = updatepid
                    data["operations." + i + ".parameters." + j + ".description"] = updatedescription
                    data["operations." + i + ".parameters." + j + ".value"] = updatevalue
                    console.log(data)
                    collection.update({
                        "_id": "04"
                    },{
                        $set: data
                    })
                    .then(dbModel => res.json(dbModel))
                }
            }
        }
    }
})

答案 3 :(得分:1)

从mongo 3.6版开始,您可以将 $ [] $ [] 结合使用以更新嵌套数组

  

与$ []一起更新嵌套数组

     

$ []过滤的位置运算符,与   所有$ []位置运算符都可用于更新嵌套数组。

https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#position-nested-arrays-filtered