一般范围和说明
您在此处执行的操作存在一些问题。首先是您的查询条件。您指的是几个您不需要的 _id 值,并且至少其中一个不在顶层。
为了进入“嵌套”值并假设_id 值是唯一的并且不会出现在任何其他文档中,您的查询表单应该是这样的:
Model.update(
{ "array1.array2._id": "123" },
{ "$push": { "array1.0.array2.$.answeredBy": "success" } },
function(err,numAffected) {
// something with the result in here
}
);
现在这实际上会起作用,但实际上它只是一个侥幸,因为有很好的理由说明它不适合你。
重要的阅读是在“嵌套数组”主题下的positional $ 运算符的官方文档中。这是什么意思:
位置 $ 运算符不能用于遍历多个数组的查询,例如遍历嵌套在其他数组中的数组的查询,因为 $ 占位符的替换是单个值
具体而言,这意味着将在位置占位符中匹配并返回的元素是 first 匹配数组中的索引值。这意味着在您的情况下,“顶级”数组上的匹配索引。
因此,如果您查看所示的查询符号,我们已经“硬编码”了顶级数组中的 first (或 0 索引)位置,恰好匹配的元素在“array2”也是零索引条目。
为了证明这一点,您可以将匹配的_id 值更改为“124”,结果将$push 一个新条目添加到具有_id“123”的元素上,因为它们都在“”的零索引条目中array1",这是返回给占位符的值。
这就是嵌套数组的普遍问题。您可以删除其中一个级别,您仍然可以 $push 到“顶部”数组中的正确元素,但仍然会有多个级别。
尽量避免嵌套数组,因为你会遇到更新问题,如图所示。
一般情况是把你“认为”的东西“扁平化”为“层次”,实际上使这些“属性”在最终的细节项目上。例如,问题中结构的“扁平化”形式应该是这样的:
{
"answers": [
{ "by": "success", "type2": "123", "type1": "12" }
]
}
或者即使接受内部数组只是$push,并且永远不会更新:
{
"array": [
{ "type1": "12", "type2": "123", "answeredBy": ["success"] },
{ "type1": "12", "type2": "124", "answeredBy": [] }
]
}
这两者都适合positional $ operator范围内的原子更新
MongoDB 3.6 及以上版本
从 MongoDB 3.6 开始,有一些新功能可用于处理嵌套数组。这使用positional filtered $[<identifier>] 语法来匹配特定元素并通过更新语句中的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" }]
}
)
"arrayFilters" 传递给.update() 的选项,甚至
.updateOne()、.updateMany()、.findOneAndUpdate() 或 .bulkWrite() 方法指定匹配更新语句中给出的标识符的条件。任何符合给定条件的元素都会被更新。
因为结构是“嵌套的”,所以我们实际上使用“多个过滤器”,正如所示的过滤器定义的“数组”所指定的那样。标记的“标识符”用于匹配语句更新块中实际使用的positional filtered $[<identifier>] 语法。在这种情况下,inner 和 outer 是用于嵌套链中指定的每个条件的标识符。
这个新的扩展使得嵌套数组内容的更新成为可能,但它对“查询”此类数据的实用性并没有真正的帮助,因此与前面解释的相同的警告也适用。
你通常真的“意思”来表达“属性”,即使你的大脑最初认为是“嵌套”,它通常只是对你认为“以前的关系部分”如何组合在一起的反应。实际上,您确实需要更多的非规范化。
另请参阅How to Update Multiple Array Elements in mongodb,因为这些新的更新运算符实际上匹配和更新“多个数组元素”,而不仅仅是第一个,后者是之前的位置更新操作。
注意有点讽刺的是,由于这是在 .update() 和类似方法的“选项”参数中指定的,因此语法通常与所有最新发布的驱动程序版本兼容。
但是,mongo shell 并非如此,因为该方法在那里实现的方式(“具有讽刺意味的是,为了向后兼容”)arrayFilters 参数无法被解析选项的内部方法识别和删除为了提供与先前 MongoDB 服务器版本的“向后兼容性”和“遗留”.update() API 调用语法。
因此,如果您想在 mongo shell 或其他“基于 shell”的产品(尤其是 Robo 3T)中使用该命令,您需要开发分支或生产版本中 3.6 或更高版本的最新版本。
另请参阅positional all $[],它也更新“多个数组元素”,但不应用于指定条件,并应用于数组中所需操作的所有元素。