mongodbrobo3t

set and updateMany behaviour with array of objects in mongoDB document


I have a document in mongoDB which contains these two arrays-

"CS_Currency": [
        {
            "name": "AUD"
        },
        {
            "name": "IVP"
        }
    ],
    "TestRef": [
        {
            "name": "Reftest"
        },
        {
            "name": "Ref5"
        },
        {
            "name": "RefChanged"
        }
    ],
 "Entity_Type_Value": [
      {
        "name": "Deal"
      }
    ],

Now when I write

db.getCollection('pipeline-entity').updateMany(
    { "TestRef.name": "RefChanged", "Entity_Type_Value.name":"Deal"},  
    { $set: { "TestRef.$.name": "Ref3" }
    })

this query runs fine and third element of TestRef array is modified

BUT when I run this query---

db.getCollection('pipeline-entity').updateMany(
    { "CS_Currency.name": "IVP", "Entity_Type_Value.name":"Deal"},  
    { $set: { "CS_Currency.$.name": "IVPChanged" }
    })

this changes the first element of array i.e., AUD to IVPChanged

Can you tell why this is behaving like this? Or if I am doing something wrong?


Solution

  • Entity_Type_Value is not in your example data but it's part of your selection query for the update. And it's probably an array of {"name": "Deal"}, etc. So that affects the $-position during the update.

    If was a just a single object, like "Entity_Type_Value": { "name": "Deal" } then it works as expected.

    However, if it's also an array like: "Entity_Type_Value": [{ "name": "Deal" }, { "name": "No Deal" }] then it does affect the result & which element gets updated - mongo playground with Minimal Reproducible Example for the issue described.

    This actually happens for both your queries. In the first query, it just happens to match the correct item to update by chance/fluke, possibly based on the index of the 2nd array match clause.

    Can you tell why this is behaving like this? Or if I am doing something wrong?

    From the docs (emphasis mine):

    Multiple Array Matches

    The positional $ update operator behaves ambiguously when filtering on multiple array fields.

    When the server executes an update method, it first runs a query to determine which documents you want to update. If the update filters documents on multiple array fields, the subsequent call to the positional $ update operator doesn't always update the required position in the array.

    The docs also have an example which shows the incorrect behaviour when doing this.

    Solutions:

    1. Change the data structure:

    If you are expecting some sort of ordered relation between each array in your object, or between some of the arrays in your object. ie CS_Currency[0] relates to Entity_Type_Value[0] and maybe relates to TestRef[0] - this is wrong. They should all be in one object and you can still have an array of them:

    {
      "CS_Currency": [
        {
          "name": "AUD",
          "Entity_Type": "No Deal",
          "TestRef": "Reftest"
        },
        {
          "name": "IVP",
          "Entity_Type": "Deal",
          "TestRef": "Ref5"
        }
      ]
    }
    

    2. Fix the query IF-AND-ONLY-IF there will be just one object in the Entity_Type_Value array:

    Put a 0 for the "Entity_Type_Value" match to match against just the first element of that array:

    db.collection.update({
      "CS_Currency.name": "IVP",
      // explcit match against FIRST element of Entity_Type_Value
      "Entity_Type_Value.0.name": "Deal"
    },
    {
      $set: {
        "CS_Currency.$.name": "IVPChanged"
      }
    })
    

    Mongo Playground