使用 MongoDB 更新嵌套数组
Posted
技术标签:
【中文标题】使用 MongoDB 更新嵌套数组【英文标题】:Updating a Nested Array with MongoDB 【发布时间】:2020-05-05 14:32:51 【问题描述】:我正在尝试更新嵌套数组中的值,但无法使其正常工作。
我的对象是这样的
"_id":
"$oid": "1"
,
"array1": [
"_id": "12",
"array2": [
"_id": "123",
"answeredBy": [], // need to push "success"
,
"_id": "124",
"answeredBy": [],
],
]
我需要将一个值推送到“answeredBy”数组。
在下面的示例中,我尝试将“成功”字符串推送到“123 _id”对象的“answeredBy”数组,但它不起作用。
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);
我找到了这个link,但它的回答只是说我应该使用类似结构的对象而不是数组。这不适用于我的情况。我真的需要我的对象嵌套在数组中
如果你能在这里帮助我,那就太好了。我一直在花费数小时来解决这个问题。
提前谢谢你!
【问题讨论】:
【参考方案1】:一般范围和说明
您在此处执行的操作存在一些问题。首先是您的查询条件。您指的是几个您不需要的 _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 $[]
,它也更新“多个数组元素”,但不应用于指定条件,并应用于数组中所需操作的所有元素。
【讨论】:
谢谢,您对 arrayFilters 的解释比 mongoDB 站点文档要好得多。他们只是没有演示嵌套数组更新是如何工作的。 哇!你是个天才!你的解释是如此清晰和直截了当。谢谢!【参考方案2】:我知道这是一个非常古老的问题,但我自己只是在努力解决这个问题,并找到了我认为的更好的答案。
解决此问题的一种方法是使用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()
,两者都工作得很好。
【讨论】:
我认为值得添加以供参考和理解,有充分的理由说明这可能不是问题的“更好”答案。这里本质上发生的是从服务器“读取”文档,对数据结构进行修改,然后“写”回。尽管使用了 helper,但这在任何语言中都不难做到,但这通常是个坏主意。您通常不希望这样做,因为您不能保证在此代码进行更改时文档在服务器上没有更改。这就是为什么首选服务器“更新”操作的原因。以上是关于使用 MongoDB 更新嵌套数组的主要内容,如果未能解决你的问题,请参考以下文章
如何在 mongodb/mongoose 的嵌套数组中检查是不是存在并更新对象?