skip to Main Content

We currently store lots of project-related data in a single document as referencing is a lot more painful than nesting. Every entity has it’s own Id. Usually we know exactly which entity we’re updating and where it is present in the overall document.

But now I have to update an entity type which is nested in lots of different entities and thus is scattered all around the document. I have it’s specific Id but I do not know where it’s inside my document unless I iterate over my whole model to find out.

Is there a way in MongoDB to say, that it shoud look for a specific Id and do all operations on that level? I know there are positional operators but it’s possible that I need to update a document that’s nested below multiple arrays.

I could find the entity inside my model when loaded into the backend and then generate a full path with array filters via reflection but that’s quite a lot to implement and not very well optimized. If this can be handled by MongoDB itself that’d be great.

Example document:

{
    "_id": ObjectId("649aae77854d7aaafdedc73e"),
    "SomeEntity": {
        "_id": ObjectId("649aae89f5959c68c86d0a3e"),
        "Array": [
            {
                "_id": ObjectId("649aae9e515f2da2b4113714"),
                "FieldToChange": false
            },
            {
                "_id": ObjectId("649aaeb8c1e2c4076e6d6f7a"),
                "FieldToChange": false
            }
        ]
    },
    "Data": [
        {
            "_id": ObjectId("649aaecddd2d05f3aaf5041e"),
            "FieldToChange": false
        },
        {
            "_id": ObjectId("649aaecfec20fbd88a41dc97"),
            "FieldToChange": false
        }
    ]
}

The elements below Array and Data are the same entity type/schema. Now I need to update let’s say the one with Id 649aae9e515f2da2b4113714 to have it’s FieldToChange value set to true. Imagine this is stored even deeper maybe even nested in multiple arrays.

Any way to do this the easy way utilizing MongoDB? Or do I have to build around that manually in the backend?

2

Answers


  1. I think for your case it would be best to fetch the whole record and change the value with a script written in some language like JavaScript(Node JS), and after updating it just replace the object with the updated object.

    Login or Signup to reply.
  2. A more nested example document:

    {
      "_id": {
        "$oid": "649aae77854d7aaafdedc73e"
      },
      "SomeEntity": {
        "_id": {
          "$oid": "649aae89f5959c68c86d0a3e"
        },
        "Array": [
          {
            "_id": {
              "$oid": "649aae9e515f2da2b4113714"
            },
            "Field1": {
              "_id": {
                "$oid": "649aae9e515f2da2b411372b"
              },
              "Field2": {
                "_id": {
                  "$oid": "649aae9e515f2da2b4113b2b"
                },
                "FieldToChange": false
              }
            }
          },
          {
            "_id": {
              "$oid": "649aaeb8c1e2c4076e6d6f7a"
            },
            "Field1": {
              "_id": {
                "$oid": "649aae9e515f2da2b41137cb"
              },
              "Field2": {
                "_id": {
                  "$oid": "649aae9e515f2da2b41e3b2b"
                },
                "FieldToChange": false
              }
            }
          }
        ]
      },
      "Data": [
        {
          "Field1": {
            "_id": {
              "$oid": "649aae9e515f2da2b4c1a72b"
            },
            "FieldToChange": false
          }
        },
        {
          "_id": {
            "$oid": "649aaecfec20fbd88a41dc97"
          },
          "Field1": {
            "_id": {
              "$oid": "649aae9e695f2da2b4c1a72c"
            },
            "FieldToChange": false
          }
        }
      ]
    }
    

    document

    The solution

    As long as you know the schema of the document, I think it should be fine building up queries.

    db.demo.updateOne({},{$set: {"SomeEntity.Array.$[elem1].Field1.Field2.FieldToChange": true,"Data.$[elem3].Field1.FieldToChange": true}},{arrayFilters: [{ "elem1.Field1.Field2._id": ObjectId("649aae9e695f2da2b4c1a72c")},{ "elem3.Field1._id": ObjectId("649aae9e695f2da2b4c1a72c") }]})
    

    properly expanded as:

    db.demo.updateOne(
      {},
      {
        $set: {
          "SomeEntity.Array.$[elem1].Field1.Field2.FieldToChange": true,
          "Data.$[elem3].Field1.FieldToChange": true
        }
      },
      {
        arrayFilters: [
          { "elem1.Field1.Field2._id": ObjectId("649aae9e695f2da2b4c1a72c") },
          { "elem2.Field1._id": ObjectId("649aae9e695f2da2b4c1a72c") }
        ]
      }
    )
    

    The first parameter {} catches all the documents. – Query Filter

    The second parameter { $set: { ... }} defines what and where you want to update. – Update Operations

    The third parameter { arrayFilters: ... } is the conditional statement. The arrayFilters filter out which element got the specified _id. Then on that particular element, the second parameter is executed. – Options

    Here the arrayFilters finds the object which contains the Field1 -> Field2 -> FieldToChange._id (in SomeEntity) or Field1 -> FieldToChange._id (in Data), given the value of FieldToChange._id that needs to be modified.

    For a given document like below,

    {
      "_id": {
        "$oid": "649aae77854d7aaafdedc73e"
      },
      "A": {
        "_id": {
          "$oid": "649aae89f5959c68c86d0a3e"
        },
        "B": [
          {
            "_id": {
              "$oid": "649aae9e515f2da2b4113714"
            },
            "C": {
              "_id": {
                "$oid": "649aae9e515f2da2b411372b"
              },
              "D": {
                "_id": {
                  "$oid": "649aae9e515f2da2b4113b2b"
                },
                "X": false,
                "Y": false
              }
            }
          },
    ...
    

    if you only want to update value of X you have to mention only X then you must not mention Y in the query as attributes don’t have a unique _id.

    db.demo.updateOne(
      {},
      {
        $set: {
          "A.B.$[elem1].C.D.X": true,
          "P.$[elem2].Q.X": true
        }
      },
      {
        arrayFilters: [
          { "elem1.Field1.Field2._id": ObjectId("649aae9e695f2da2b4c1a72c") },
          { "elem2.Field1._id": ObjectId("649aae9e695f2da2b4c1a72c") }
        ]
      }
    )
    

    Execution

    execution

    Result

    result

    Edit

    I modified the query as the pevious one I provided wasn’t working for all scenarios. Here firstly keep all possible subdocuments you that can be updated. Then in the query only pass the ones with final attribute you want to be changed (As mentioned above).

    Hope this helps

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search