Search code examples
mongodbmongo-java-driver

Mongo update multiple nested objects when their ID exists in a list


I'm trying to update many objects within a nested array when their IDs exist in a list.

The document structure is like so:

{
    id: p00,
    children: [{
            id: c00,
            value: true
        },
        {
            id: c01,
            value: true
        },
        {
            id: c02,
            value: true
        }
    ]
}

I would like to set value=false if id=p00 and children.id in [c00, c01].

This is my attempt, however it's clearly updating making only 1 update to the parent record, meaning only the c00 would be modified with the false value.

collection
    .updateMany(and(
        eq("id", "p00"),
        in("children._id", [c00, c01])), 
    combine(
        set("children.$.value", false)
));

Is this possible, or do I need to move the children up to make a more flatend structure? I'd prefer not to as that introduces it's own problems.

Thanks!


Solution

  • You can do it with pipeline update.

    Pipeline updates allows us to use all aggregate operators, so we can do complicated updates (in your case maybe its possible with update operators also, but with pipeline its easy)

    Pipelines updates require >= MongoDB 4.2.

    Test code here

    Query

    • match on id=p00
    • map each member($$c variable) of the children array
      if $$c.id in the list ["c00","c01"]
      then member merge $$c with {"value" : false} (its like add key value pair)
      else dont change $$c
    db.collection.update({
      "id": {
        "$eq": "p00"
      }
    },
    [
      {
        "$set": {
          "children": {
            "$map": {
              "input": "$children",
              "as": "c",
              "in": {
                "$cond": [
                  {
                    "$in": [
                      "$$c.id",
                      [
                        "c00",
                        "c01"
                      ]
                    ]
                  },
                  {
                    "$mergeObjects": [
                      "$$c",
                      {
                        "value": false
                      }
                    ]
                  },
                  "$$c"
                ]
              }
            }
          }
        }
      }
    ])
    

    If you dont want to use update pipeline(your update is simple and it can be done with array filters also), and in Java would be like bellow.

    coll.updateOne(new Document().append("id","p00"),
                   new Document().append("$set",new Document().append("children.$[item].value",false)),
                   new UpdateOptions().arrayFilters(
                     Collections.singletonList( Filters.in("item.id",Arrays.asList("c00","c01")))))