Updating a Nested Array with MongoDB












21















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.



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



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!










share|improve this question





























    21















    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.



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



    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!










    share|improve this question



























      21












      21








      21


      8






      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.



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



      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!










      share|improve this question
















      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.



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



      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!







      javascript node.js mongodb mongoose mongodb-query






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Sep 10 '17 at 22:15









      Neil Lunn

      100k23177187




      100k23177187










      asked May 10 '14 at 4:32









      masanorinyomasanorinyo

      3722420




      3722420
























          2 Answers
          2






          active

          oldest

          votes


















          34














          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.



          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:





          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.



          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.



          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.



          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.



          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:



           {
          "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:



          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.



          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.




          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.



          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.



          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.




          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.






          share|improve this answer


























          • thanks, your explanation of arrayFilters is so much better than the mongoDB site documentation. they just don't demonstrate how a nested array update would work.

            – Nir
            Jul 29 '18 at 8:33



















          5














          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.



          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.






          share|improve this answer



















          • 12





            I suppose it is worth adding for reference and understanding that there is a good reason why this may not be a "better" answer to the problem. What is essentially happening here is "reading" the document from the server, making modifications to the data structure and then "writing" back. Despite the helper usage, this is not difficult to do in any language, but it's usually a bad idea. You generally don't want to do this as you cannot guarantee that the document has not changed on the server while this code was making a change. That is why server "update" operations are preferred.

            – Neil Lunn
            Dec 31 '14 at 23:06











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f23577123%2fupdating-a-nested-array-with-mongodb%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          2 Answers
          2






          active

          oldest

          votes








          2 Answers
          2






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          34














          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.



          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:





          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.



          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.



          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.



          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.



          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:



           {
          "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:



          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.



          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.




          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.



          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.



          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.




          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.






          share|improve this answer


























          • thanks, your explanation of arrayFilters is so much better than the mongoDB site documentation. they just don't demonstrate how a nested array update would work.

            – Nir
            Jul 29 '18 at 8:33
















          34














          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.



          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:





          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.



          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.



          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.



          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.



          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:



           {
          "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:



          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.



          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.




          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.



          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.



          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.




          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.






          share|improve this answer


























          • thanks, your explanation of arrayFilters is so much better than the mongoDB site documentation. they just don't demonstrate how a nested array update would work.

            – Nir
            Jul 29 '18 at 8:33














          34












          34








          34







          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.



          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:





          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.



          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.



          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.



          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.



          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:



           {
          "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:



          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.



          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.




          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.



          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.



          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.




          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.






          share|improve this answer















          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.



          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:





          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.



          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.



          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.



          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.



          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:



           {
          "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:



          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.



          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.




          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.



          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.



          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.




          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.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Apr 29 '18 at 3:14

























          answered May 10 '14 at 5:00









          Neil LunnNeil Lunn

          100k23177187




          100k23177187













          • thanks, your explanation of arrayFilters is so much better than the mongoDB site documentation. they just don't demonstrate how a nested array update would work.

            – Nir
            Jul 29 '18 at 8:33



















          • thanks, your explanation of arrayFilters is so much better than the mongoDB site documentation. they just don't demonstrate how a nested array update would work.

            – Nir
            Jul 29 '18 at 8:33

















          thanks, your explanation of arrayFilters is so much better than the mongoDB site documentation. they just don't demonstrate how a nested array update would work.

          – Nir
          Jul 29 '18 at 8:33





          thanks, your explanation of arrayFilters is so much better than the mongoDB site documentation. they just don't demonstrate how a nested array update would work.

          – Nir
          Jul 29 '18 at 8:33













          5














          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.



          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.






          share|improve this answer



















          • 12





            I suppose it is worth adding for reference and understanding that there is a good reason why this may not be a "better" answer to the problem. What is essentially happening here is "reading" the document from the server, making modifications to the data structure and then "writing" back. Despite the helper usage, this is not difficult to do in any language, but it's usually a bad idea. You generally don't want to do this as you cannot guarantee that the document has not changed on the server while this code was making a change. That is why server "update" operations are preferred.

            – Neil Lunn
            Dec 31 '14 at 23:06
















          5














          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.



          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.






          share|improve this answer



















          • 12





            I suppose it is worth adding for reference and understanding that there is a good reason why this may not be a "better" answer to the problem. What is essentially happening here is "reading" the document from the server, making modifications to the data structure and then "writing" back. Despite the helper usage, this is not difficult to do in any language, but it's usually a bad idea. You generally don't want to do this as you cannot guarantee that the document has not changed on the server while this code was making a change. That is why server "update" operations are preferred.

            – Neil Lunn
            Dec 31 '14 at 23:06














          5












          5








          5







          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.



          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.






          share|improve this answer













          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.



          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.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Oct 22 '14 at 10:09









          Jesper NielsenJesper Nielsen

          5326




          5326








          • 12





            I suppose it is worth adding for reference and understanding that there is a good reason why this may not be a "better" answer to the problem. What is essentially happening here is "reading" the document from the server, making modifications to the data structure and then "writing" back. Despite the helper usage, this is not difficult to do in any language, but it's usually a bad idea. You generally don't want to do this as you cannot guarantee that the document has not changed on the server while this code was making a change. That is why server "update" operations are preferred.

            – Neil Lunn
            Dec 31 '14 at 23:06














          • 12





            I suppose it is worth adding for reference and understanding that there is a good reason why this may not be a "better" answer to the problem. What is essentially happening here is "reading" the document from the server, making modifications to the data structure and then "writing" back. Despite the helper usage, this is not difficult to do in any language, but it's usually a bad idea. You generally don't want to do this as you cannot guarantee that the document has not changed on the server while this code was making a change. That is why server "update" operations are preferred.

            – Neil Lunn
            Dec 31 '14 at 23:06








          12




          12





          I suppose it is worth adding for reference and understanding that there is a good reason why this may not be a "better" answer to the problem. What is essentially happening here is "reading" the document from the server, making modifications to the data structure and then "writing" back. Despite the helper usage, this is not difficult to do in any language, but it's usually a bad idea. You generally don't want to do this as you cannot guarantee that the document has not changed on the server while this code was making a change. That is why server "update" operations are preferred.

          – Neil Lunn
          Dec 31 '14 at 23:06





          I suppose it is worth adding for reference and understanding that there is a good reason why this may not be a "better" answer to the problem. What is essentially happening here is "reading" the document from the server, making modifications to the data structure and then "writing" back. Despite the helper usage, this is not difficult to do in any language, but it's usually a bad idea. You generally don't want to do this as you cannot guarantee that the document has not changed on the server while this code was making a change. That is why server "update" operations are preferred.

          – Neil Lunn
          Dec 31 '14 at 23:06


















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f23577123%2fupdating-a-nested-array-with-mongodb%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          這個網誌中的熱門文章

          Hercules Kyvelos

          Tangent Lines Diagram Along Smooth Curve

          Yusuf al-Mu'taman ibn Hud