用MongoDB更新嵌套数组

我想更新嵌套数组中的值,但不能得到它的工作。

我的目标是这样的

{ "_id": { "$oid": "1" }, "array1": [ { "_id": "12", "array2": [ { "_id": "123", "answeredBy": [], }, { "_id": "124", "answeredBy": [], } ], } ] } 

我需要推送一个值为“answersBy”数组。

在下面的例子中,我尝试将“成功”string推送到“123_id”对象的“answersBy”数组,但它不起作用。

 callback = function(err,value){ if(err){ res.send(err); }else{ res.send(value); } }; conditions = { "_id": 1, "array1._id": 12, "array2._id": 123 }; updates = { $push: { "array2.$.answeredBy": "success" } }; options = { upsert: true }; Model.update(conditions, updates, options, callback); 

我发现这个链接 ,但它的答案只是说我应该使用对象结构,而不是数组的。 这不能适用于我的情况。 我真的需要我的对象嵌套在数组中

如果你能帮我在这里,这将是非常好的。 我花了好几个小时才弄明白这一点。

先谢谢你!

一般范围和解释

你在这里做什么有一些问题。 首先你的查询条件。 您指的是您不需要的多个_id值,其中至less有一个不在顶层。

为了进入一个“嵌套”的价值,也假设_id值是唯一的,不会出现在任何其他文件,你的查询forms应该是这样的:

 Model.update( { "array1.array2._id": "123" }, { "$push": { "array1.0.array2.$.answeredBy": "success" } }, function(err,numAffected) { // something with the result in here } ); 

现在,这实际上是可行的,但它真的只是一个侥幸,因为有很好的理由,为什么它不应该为你工作。

重要的阅读材料在“嵌套arrays”主题下的位置$操作员的官方文档中。 这说的是:

位置$运算符不能用于遍历多个数组的查询,例如遍历嵌套在其他数组中的查询,因为replace$ placeholder是单个值

具体来说,这意味着要在位置占位符中匹配和返回的元素是第一个匹配数组的索引值。 这意味着在你的情况下,“顶部”水平数组上的匹配索引。

因此,如果您查看所示的查询符号,我们已经对顶级数组中的第一个 (或第0个索引)位置进行了“硬编码”,而且“array2”内的匹配元素也是零索引条目。

为了certificate这一点,你可以改变匹配的_id值为“124”,结果将$push一个新的条目到_id “123”的元素,因为它们都在“array1”的零索引条目,这是返回的值到占位符。

所以这是嵌套数组的一般问题。 你可以删除其中一个关卡,你仍然可以将$push到你的“顶部”数组中的正确元素,但是仍然会有多个关卡。

尝试避免嵌套数组,因为您将遇到更新问题,如图所示。

一般情况下,“扁平化”你认为是“关卡”的东西,实际上把这些“属性”写在最后的细节项目上。 例如,问题中结构的“扁平化”forms应该是这样的:

  { "answers": [ { "by": "success", "type2": "123", "type1": "12" } ] } 

或者甚至当接受内部数组只是$push ,并从来没有更新:

  { "array": [ { "type1": "12", "type2": "123", "answeredBy": ["success"] }, { "type1": "12", "type2": "124", "answeredBy": [] } ] } 

这两者都适用于位置$运算符范围内的primefaces更新


MongoDB 3.6及以上版本

从MongoDB 3.6开始,可以使用嵌套数组的新function。 这使用位置过滤的$[<identifier>]语法来匹配特定元素,并通过update语句中的arrayFilters应用不同的条件:

 Model.update( { "_id": 1, "array1": { "$elemMatch": { "_id": "12","array2._id": "123" } } }, { "$push": { "array1.$[outer].array2.$[inner].answeredBy": "success" } }, { "arrayFilters": [{ "outer._id": "12" },{ "inner._id": "123" }] } ) 

传递给.update()或甚至.updateOne() .updateMany() .findOneAndUpdate().bulkWrite()方法的选项的"arrayFilters"指定了在update语句中给出的标识符上匹配的条件。 任何符合条件的元素都将被更新。

因为结构是“嵌套的”,所以我们实际上使用了“多个filter”,就像filter定义的“数组”一样。 标记的“标识符”用于匹配语句的更新块中实际使用的位置过滤的$[<identifier>]语法。 在这种情况下, innerouter是用于嵌套链指定的每个条件的标识符。

这种新的扩展使嵌套数组内容的更新成为可能,但是它并不能真正帮助“查询”这些数据的实用性,所以同样的注意事项也适用于前面所述。

即使你的大脑最初认为是“嵌套”,你通常真正的“意思”是expression为“属性”,这只是对你如何相信“以前的关系部分”聚集在一起的反应。 实际上你真的需要更多的非规范化。

另请参阅如何在mongodb中更新多个数组元素 ,因为这些新的更新操作符实际上匹配和更新“多个数组元素”,而不是第一个位置更新的前一个操作。

注意有点讽刺的是,因为这是在.update()和类似的方法的“options”参数中指定的,所以语法通常与所有最新的版本驱动程序版本兼容。

然而,对于mongo shell来说,这是不正确的,因为在那里实现方法的方式(“为了向后兼容性而讽刺地”), arrayFilters参数不能被内部方法识别和移除,parsing选项以提供“向后兼容性”与之前的MongoDB服务器版本和“传统” .update() API调用语法。

所以如果你想在mongo shell或其他“基于shell”的产品(特别是Robo 3T)中使用这个命令,你需要开发分支或生产版本3.6或更高版本的最新版本。

另请参阅positional all $[] ,它也更新“多个数组元素”,但不应用于指定的条件,并适用于数组中所有需要操作的元素。

我知道这是一个非常古老的问题,但是我自己也只是在努力解决这个问题,并且发现我相信是一个更好的答案。

解决这个问题的方法是使用Sub-Documents 。 这是通过在模式中嵌套模式来完成的

 MainSchema = new mongoose.Schema({ array1: [Array1Schema] }) Array1Schema = new mongoose.Schema({ array2: [Array2Schema] }) Array2Schema = new mongoose.Schema({ answeredBy": [...] }) 

这样,对象将看起来像你展示的那个,但是现在每个数组都填充有子文档。 这样就可以将自己的想法变成您想要的子文档。 而不是使用.update然后使用.find.findOne来获取您想要更新的文档。

 Main.findOne(( { _id: 1 } ) .exec( function(err, result){ result.array1.id(12).array2.id(123).answeredBy.push('success') result.save(function(err){ console.log(result) }); } ) 

没有使用这种方法.push( )我自己,所以语法可能不正确,但我已经使用.set().remove() ,并且都工作得很好。