更新数组mongodb内的嵌套数组[重复]

Posted

技术标签:

【中文标题】更新数组mongodb内的嵌套数组[重复]【英文标题】:Updating nested array inside array mongodb [duplicate] 【发布时间】:2015-06-20 11:09:33 【问题描述】:

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

【问题讨论】:

MongoDB 不支持匹配到多个级别的数组。考虑改变您的文档模型,使每个文档代表一个操作,其中包含操作文档中重复的一组操作的通用信息。 【参考方案1】:

MongoDB 3.6 及更新版本

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

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

目前这似乎无法使用聚合框架。有一个未解决的打开 JIRA issue 链接到它。但是,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"
        
    
);

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

【讨论】:

这确实有效,但是我有什么方法可以即时创建 $set 条件吗?可以帮助我获取对象的索引然后进行相应修改的东西? 我想知道我是否有可能实现他们在这里建议的东西***.com/questions/28756041/… @SantiagoEsquivel 很有可能。稍后将提供解决方案,但本质上是使用 MapReduce 获取带有数组索引的投影,并使用该投影中的索引值来动态更新数组元素。 好的,我一直在尝试复制他们在该答案中解释的内容,但没有取得多大成功。如果您今天晚些时候有时间回答,我会继续尝试,并希望我能接近您的解决方案。再次感谢。 @SantiagoEsquivel 我已经更新了答案以包含面向 MapReduce 解决方案的方法。【参考方案2】:

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

使用 $[] 更新嵌套数组

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

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

【讨论】:

【参考方案3】:

我们将尝试找到外部数组(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))
                
            
        
    
)

【讨论】:

【参考方案4】:

如果是经常变化的数据,你应该把结构扁平化,把变化很大的数据和没有变化的数据分开。

如果是不经常变化的数据,而且整个数据对象不是海量的,只要在客户端修改对象,更新整个对象即可。

【讨论】:

以上是关于更新数组mongodb内的嵌套数组[重复]的主要内容,如果未能解决你的问题,请参考以下文章

如何更新嵌套数组MongoDb Node js中的必填字段[重复]

嵌套数组内的Mongodb增量值

更新 mongodb 中的嵌套数组

使用 MongoDB 更新嵌套数组

使用 MongoDB 更新嵌套数组

MongoDB - 基于嵌套数组的字段值更新数组对象中的字段