使用MongoDB更新嵌套数组

时间:2022-07-02 02:42:22

I am trying to update a value in the nested array but can't get it to work.

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

My object is like this

我的目标是这样的

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

I need to push a value to "answeredBy" array.

我需要将一个值推入“answeredBy”数组。

In the below example, I tried pushing "success" string to the "answeredBy" array of the "123 _id" object but it does not work.

在下面的示例中,我尝试将“success”字符串推到“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);

I found this link, but its answer only says I should use object like structure instead of array's. This cannot be applied in my situation. I really need my object to be nested in arrays

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

It would be great if you can help me out here. I've been spending hours to figure this out.

如果你能在这里帮助我就太好了。我花了好几个小时才弄明白。

Thank you in advance!

提前谢谢你!

2 个解决方案

#1


19  

General Scope and Explanation

There are a few things wrong with what you are doing here. Firstly your query conditions. You are referring to several _id values where you should not need to, and at least one of which is not on the top level.

你在这里做的事情有一些问题。首先您的查询条件。您指的是不需要的几个_id值,其中至少有一个不位于顶层。

In order to get into a "nested" value and also presuming that _id value is unique and would not appear in any other document, you query form should be like this:

为了进入“嵌套”值,并假定_id值是唯一的,并且不会出现在任何其他文档中,您的查询表单应该如下所示:

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

Now that would actually work, but really it is only a fluke that it does as there are very good reasons why it should not work for you.

这实际上是可行的,但这只是侥幸,因为有很好的理由说明它不适合你。

The important reading is in the official documentation for the positional $ operator under the subject of "Nested Arrays". What this says is:

重要的阅读是“嵌套数组”主题下的位置$操作符的官方文档。这说的是什么:

The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value

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

Specifically what that means is the element that will be matched and returned in the positional placeholder is the value of the index from the first matching array. This means in your case the matching index on the "top" level array.

具体来说,这意味着将在位置占位符中被匹配和返回的元素是来自第一个匹配数组的索引的值。这意味着在您的例子中,匹配索引位于“top”级别数组中。

So if you look at the query notation as shown, we have "hardcoded" the first ( or 0 index ) position in the top level array, and it just so happens that the matching element within "array2" is also the zero index entry.

如果你看一下查询表示法,我们有“硬编码”顶层数组中的第一个(或0个索引)位置,碰巧“array2”中的匹配元素也是零索引条目。

To demonstrate this you can change the matching _id value to "124" and the result will $push an new entry onto the element with _id "123" as they are both in the zero index entry of "array1" and that is the value returned to the placeholder.

为了演示这一点,您可以将匹配的_id值更改为“124”,结果将$将一个新条目推入带有_id“123”的元素,因为它们都位于“array1”的零索引条目中,即返回给占位符的值。

So that is the general problem with nesting arrays. You could remove one of the levels and you would still be able to $push to the correct element in your "top" array, but there would still be multiple levels.

这就是嵌套数组的一般问题。您可以删除其中一个级别,并且仍然可以对“top”数组中的正确元素进行$push,但是仍然有多个级别。

Try to avoid nesting arrays as you will run into update problems as is shown.

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

The general case is to "flatten" the things you "think" are "levels" and actually make theses "attributes" on the final detail items. For example, the "flattened" form of the structure in the question should be something like:

一般的情况是把你“认为”是“层次”的东西“拉平”,并在最终的细节项目上真正地制造出这些“属性”。例如,问题中结构的“扁平”形式应该是这样的:

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

Or even when accepting the inner array is $push only, and never updated:

甚至在接受内部数组时,只需要$push,并且从不更新:

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

Which both lend themselves to atomic updates within the scope of the positional $ operator

在位置$操作符的范围内,哪两个都可以进行原子更新


MongoDB 3.6 and Above

From MongoDB 3.6 there are new features available to work with nested arrays. This uses the positional filtered $[<identifier>] syntax in order to match the specific elements and apply different conditions through arrayFilters in the update statement:

从MongoDB 3.6开始,可以使用嵌套数组的新特性。它使用位置过滤的$[ ]语法来匹配特定的元素,并通过更新语句中的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" }] 
  }
)

The "arrayFilters" as passed to the options for .update() or even .updateOne(), .updateMany(), .findOneAndUpdate() or .bulkWrite() method specifies the conditions to match on the identifier given in the update statement. Any elements that match the condition given will be updated.

传递给.update()甚至. updateone()、. updatemany()、. findoneandupdate()或. bulkwrite()方法的“arrayFilters”指定了在update语句中给定的标识符上匹配的条件。任何与给定条件匹配的元素都将被更新。

Because the structure is "nested", we actually use "multiple filters" as is specified with an "array" of filter definitions as shown. The marked "identifier" is used in matching against the positional filtered $[<identifier>] syntax actually used in the update block of the statement. In this case inner and outer are the identifiers used for each condition as specified with the nested chain.

由于结构是“嵌套的”,我们实际上使用了“多重过滤器”,如所示,它是用过滤器定义的“数组”指定的。标记的“标识符”用于匹配语句更新块中实际使用的位置过滤的$[ ]语法。在这种情况下,内部和外部是使用嵌套链指定的每个条件使用的标识符。

This new expansion makes the update of nested array content possible, but it does not really help with the practicality of "querying" such data, so the same caveats apply as explained earlier.

这个新的扩展使嵌套数组内容的更新成为可能,但是它并不能真正帮助“查询”此类数据,因此与前面解释的相同的注意事项也适用。

You typically really "mean" to express as "attributes", even if your brain initially thinks "nesting", it's just usually a reaction to how you believe the "previous relational parts" come together. In reality you really need more denormalization.

你通常真正的“意思”是表示“属性”,即使你的大脑最初认为是“嵌套”,它通常只是你认为“先前的关系部分”是如何结合在一起的反应。事实上,你真的需要更多的非规范化。

Also see How to Update Multiple Array Elements in mongodb, since these new update operators actually match and update "multiple array elements" rather than just the first, which has been the previous action of positional updates.

还可以看到如何在mongodb中更新多个数组元素,因为这些新的更新操作符实际上匹配并更新了“多个数组元素”,而不仅仅是第一个元素,这是位置更新的前一个动作。

NOTE Somewhat ironically, since this is specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions.

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

However this is not true of the mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update() API call syntax.

mongo shell的但是这是不正确的,因为方法实现的方式(具有讽刺意味的是为了向后兼容)arrayFilters论点并不认可,被一个内部方法,解析以提供“向后兼容性”选项前MongoDB服务器版本和一个“遗留”.update()API调用语法。

So if you want to use the command in the mongo shell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater.

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

See also positional all $[] which also updates "multiple array elements" but without applying to specified conditions and applies to all elements in the array where that is the desired action.

还可以查看position all $[],它也更新“多个数组元素”,但不应用于指定的条件,并应用于数组中的所有元素,这是所需的操作。

#2


4  

I know this is a very old question, but I just struggled with this problem myself, and found, what I believe to be, a better answer.

我知道这是一个很老的问题,但我自己也在努力解决这个问题,并且找到了一个更好的答案。

A way to solve this problem is to use Sub-Documents. This is done by nesting schemas within your schemas

解决这个问题的一种方法是使用子文档。这是通过在模式中嵌套模式来实现的

MainSchema = new mongoose.Schema({
   array1: [Array1Schema]
})

Array1Schema = new mongoose.Schema({
   array2: [Array2Schema]
})

Array2Schema = new mongoose.Schema({
   answeredBy": [...]
})

This way the object will look like the one you show, but now each array are filled with sub-documents. This makes it possible to dot your way into the sub-document you want. Instead of using a .update you then use a .find or .findOne to get the document you want to update.

这样,对象看起来就像你展示的那样,但是现在每个数组都充满了子文档。这使您可以在您想要的子文档中进行点乘。使用.find或. findone来获取想要更新的文档,而不是使用.update。

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

Haven't used the .push() function this way myself, so the syntax might not be right, but I have used both .set() and .remove(), and both works perfectly fine.

我自己还没有使用。push()函数,所以语法可能不正确,但是我同时使用了.set()和.remove(),这两种方法都很好。

#1


19  

General Scope and Explanation

There are a few things wrong with what you are doing here. Firstly your query conditions. You are referring to several _id values where you should not need to, and at least one of which is not on the top level.

你在这里做的事情有一些问题。首先您的查询条件。您指的是不需要的几个_id值,其中至少有一个不位于顶层。

In order to get into a "nested" value and also presuming that _id value is unique and would not appear in any other document, you query form should be like this:

为了进入“嵌套”值,并假定_id值是唯一的,并且不会出现在任何其他文档中,您的查询表单应该如下所示:

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

Now that would actually work, but really it is only a fluke that it does as there are very good reasons why it should not work for you.

这实际上是可行的,但这只是侥幸,因为有很好的理由说明它不适合你。

The important reading is in the official documentation for the positional $ operator under the subject of "Nested Arrays". What this says is:

重要的阅读是“嵌套数组”主题下的位置$操作符的官方文档。这说的是什么:

The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value

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

Specifically what that means is the element that will be matched and returned in the positional placeholder is the value of the index from the first matching array. This means in your case the matching index on the "top" level array.

具体来说,这意味着将在位置占位符中被匹配和返回的元素是来自第一个匹配数组的索引的值。这意味着在您的例子中,匹配索引位于“top”级别数组中。

So if you look at the query notation as shown, we have "hardcoded" the first ( or 0 index ) position in the top level array, and it just so happens that the matching element within "array2" is also the zero index entry.

如果你看一下查询表示法,我们有“硬编码”顶层数组中的第一个(或0个索引)位置,碰巧“array2”中的匹配元素也是零索引条目。

To demonstrate this you can change the matching _id value to "124" and the result will $push an new entry onto the element with _id "123" as they are both in the zero index entry of "array1" and that is the value returned to the placeholder.

为了演示这一点,您可以将匹配的_id值更改为“124”,结果将$将一个新条目推入带有_id“123”的元素,因为它们都位于“array1”的零索引条目中,即返回给占位符的值。

So that is the general problem with nesting arrays. You could remove one of the levels and you would still be able to $push to the correct element in your "top" array, but there would still be multiple levels.

这就是嵌套数组的一般问题。您可以删除其中一个级别,并且仍然可以对“top”数组中的正确元素进行$push,但是仍然有多个级别。

Try to avoid nesting arrays as you will run into update problems as is shown.

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

The general case is to "flatten" the things you "think" are "levels" and actually make theses "attributes" on the final detail items. For example, the "flattened" form of the structure in the question should be something like:

一般的情况是把你“认为”是“层次”的东西“拉平”,并在最终的细节项目上真正地制造出这些“属性”。例如,问题中结构的“扁平”形式应该是这样的:

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

Or even when accepting the inner array is $push only, and never updated:

甚至在接受内部数组时,只需要$push,并且从不更新:

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

Which both lend themselves to atomic updates within the scope of the positional $ operator

在位置$操作符的范围内,哪两个都可以进行原子更新


MongoDB 3.6 and Above

From MongoDB 3.6 there are new features available to work with nested arrays. This uses the positional filtered $[<identifier>] syntax in order to match the specific elements and apply different conditions through arrayFilters in the update statement:

从MongoDB 3.6开始,可以使用嵌套数组的新特性。它使用位置过滤的$[ ]语法来匹配特定的元素,并通过更新语句中的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" }] 
  }
)

The "arrayFilters" as passed to the options for .update() or even .updateOne(), .updateMany(), .findOneAndUpdate() or .bulkWrite() method specifies the conditions to match on the identifier given in the update statement. Any elements that match the condition given will be updated.

传递给.update()甚至. updateone()、. updatemany()、. findoneandupdate()或. bulkwrite()方法的“arrayFilters”指定了在update语句中给定的标识符上匹配的条件。任何与给定条件匹配的元素都将被更新。

Because the structure is "nested", we actually use "multiple filters" as is specified with an "array" of filter definitions as shown. The marked "identifier" is used in matching against the positional filtered $[<identifier>] syntax actually used in the update block of the statement. In this case inner and outer are the identifiers used for each condition as specified with the nested chain.

由于结构是“嵌套的”,我们实际上使用了“多重过滤器”,如所示,它是用过滤器定义的“数组”指定的。标记的“标识符”用于匹配语句更新块中实际使用的位置过滤的$[ ]语法。在这种情况下,内部和外部是使用嵌套链指定的每个条件使用的标识符。

This new expansion makes the update of nested array content possible, but it does not really help with the practicality of "querying" such data, so the same caveats apply as explained earlier.

这个新的扩展使嵌套数组内容的更新成为可能,但是它并不能真正帮助“查询”此类数据,因此与前面解释的相同的注意事项也适用。

You typically really "mean" to express as "attributes", even if your brain initially thinks "nesting", it's just usually a reaction to how you believe the "previous relational parts" come together. In reality you really need more denormalization.

你通常真正的“意思”是表示“属性”,即使你的大脑最初认为是“嵌套”,它通常只是你认为“先前的关系部分”是如何结合在一起的反应。事实上,你真的需要更多的非规范化。

Also see How to Update Multiple Array Elements in mongodb, since these new update operators actually match and update "multiple array elements" rather than just the first, which has been the previous action of positional updates.

还可以看到如何在mongodb中更新多个数组元素,因为这些新的更新操作符实际上匹配并更新了“多个数组元素”,而不仅仅是第一个元素,这是位置更新的前一个动作。

NOTE Somewhat ironically, since this is specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions.

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

However this is not true of the mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update() API call syntax.

mongo shell的但是这是不正确的,因为方法实现的方式(具有讽刺意味的是为了向后兼容)arrayFilters论点并不认可,被一个内部方法,解析以提供“向后兼容性”选项前MongoDB服务器版本和一个“遗留”.update()API调用语法。

So if you want to use the command in the mongo shell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater.

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

See also positional all $[] which also updates "multiple array elements" but without applying to specified conditions and applies to all elements in the array where that is the desired action.

还可以查看position all $[],它也更新“多个数组元素”,但不应用于指定的条件,并应用于数组中的所有元素,这是所需的操作。

#2


4  

I know this is a very old question, but I just struggled with this problem myself, and found, what I believe to be, a better answer.

我知道这是一个很老的问题,但我自己也在努力解决这个问题,并且找到了一个更好的答案。

A way to solve this problem is to use Sub-Documents. This is done by nesting schemas within your schemas

解决这个问题的一种方法是使用子文档。这是通过在模式中嵌套模式来实现的

MainSchema = new mongoose.Schema({
   array1: [Array1Schema]
})

Array1Schema = new mongoose.Schema({
   array2: [Array2Schema]
})

Array2Schema = new mongoose.Schema({
   answeredBy": [...]
})

This way the object will look like the one you show, but now each array are filled with sub-documents. This makes it possible to dot your way into the sub-document you want. Instead of using a .update you then use a .find or .findOne to get the document you want to update.

这样,对象看起来就像你展示的那样,但是现在每个数组都充满了子文档。这使您可以在您想要的子文档中进行点乘。使用.find或. findone来获取想要更新的文档,而不是使用.update。

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

Haven't used the .push() function this way myself, so the syntax might not be right, but I have used both .set() and .remove(), and both works perfectly fine.

我自己还没有使用。push()函数,所以语法可能不正确,但是我同时使用了.set()和.remove(),这两种方法都很好。