使用MongoDB更新嵌套数组

时间:2014-05-10 04:32:25

标签: javascript node.js mongodb mongoose mongodb-query

我正在尝试更新嵌套数组中的值,但无法使其工作。

我的目标是这样的

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

我需要将值推送到"已回答"阵列。

在下面的例子中,我试图推动"成功"字符串到"回答了" " 123 _id"的数组对象,但它不起作用。

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,但它的回答只是说我应该使用类似对象的结构而不是数组。这不适用于我的情况。我真的需要将我的对象嵌套在数组中

如果你能在这里帮助我,那就太好了。我花了好几个小时来搞清楚这一点。

提前谢谢!

2 个答案:

答案 0 :(得分:39)

一般范围和解释

你在这里做的事情有些不对劲。首先是您的查询条件。您指的是您不应该需要的几个_id值,并且其中至少有一个值不在顶层。

为了进入"嵌套"值并且还假设_id值是唯一的并且不会出现在任何其他文档中,您的查询表单应该是这样的:

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

现在这实际上会有效,但实际上它只是一种侥幸,因为有很好的理由说明为什么它不适合你。

重要的读物在positional $运算符的官方文档中,主题是"嵌套数组"。这说的是:

  

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

具体来说,这意味着将在位置占位符中匹配并返回的元素是第一个匹配数组中的索引值。在您的情况下,这意味着" top"级别数组。

因此,如果您查看显示的查询表示法,我们已经"硬编码"顶级数组中的第一个(或0索引)位置,恰好是" array2"中的匹配元素。也是零指数条目。

为了证明这一点,您可以将匹配的_id值更改为" 124"结果将$push _id" 123"因为它们都在" array1"的零索引条目中。这是返回占位符的值。

这就是嵌套数组的一般问题。你可以删除其中一个级别,你仍然可以$push到你的" top"中的正确元素。数组,但仍然会有多个级别。

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

一般情况是"展平"你想到的事情"是"等级"并实际制作论文"属性"关于最后的细节项目。例如,"扁平"问题中结构的形式应该是这样的:

 {
   "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" }] 
  }
)

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

因为结构是&#34;嵌套&#34;,我们实际使用&#34;多个过滤器&#34;用&#34;数组&#34;指定过滤器定义如图所示。标记的&#34;标识符&#34;用于匹配语句更新块中实际使用的positional filtered $[<identifier>]语法。在这种情况下,outer.update()是用于嵌套链指定的每个条件的标识符。

这种新的扩展使得嵌套数组内容的更新成为可能,但它并没有真正帮助实现&#34;查询&#34;这些数据,所以同样的警告适用,如前所述。

你通常真的&#34;意思是&#34;表达为&#34;属性&#34;,即使你的大脑最初认为&#34;嵌套&#34;,它通常只是对你如何相信以前的关系部分的反应&#34; ;一起来。实际上,你真的需要更多的非规范化。

另见How to Update Multiple Array Elements in mongodb,因为这些新的更新运算符实际上匹配并更新了多个数组元素&#34;而不仅仅是第一个,这是之前的位置更新操作。

  

注意有点讽刺的是,因为这是在&#34;选项&#34;中指定的。对于mongo和类似方法的参数,语法通常与所有最新的发行版驱动程序版本兼容。

     

然而,arrayFilters shell不是这样,因为在那里实现方法的方式(&#34;具有讽刺意味的是为了向后兼容&#34;).update()参数无法识别和删除通过内部方法解析选项以实现向后兼容性&#34;使用先前的MongoDB服务器版本和&#34;遗产&#34; mongo API调用语法。

     

因此,如果您想在{{1}} shell或其他基于shell的&#34;中使用该命令。产品(特别是Robo 3T)您需要3.6或更高版本的开发分支或生产版本的最新版本。

另请参阅positional all $[],它还会更新&#34;多个数组元素&#34;但不适用于指定的条件,并适用于数组中所有元素,这是所需的操作。

答案 1 :(得分:6)

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

解决此问题的方法是使用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(),两者都完美无缺。