How to combine multiple arrayFilters for a single key

67 views Asked by At

Let's say I have a schema in this format:

const some_schema = new mongoose.Schema({
    "id": { type: Number },
    "list": [
        {
            id: { type: Number },
            value: { type: Number }
        }
    ]
})

I need to update multiple objects in the list using arrayFilters given that every key in the arrayFilters is unique and will only match 1 object in the list.

I can make dynamic keys using JS like so:

//original doc values:
{
    "id": 10,
    "list": [
        { id: 1, value: 5 },
        { id: 2, value: 10 }
     ]
}

let updates = [ { id: 1, newValue: 10 }, { id: 2, newValue: 20 }];
let queryOptions = { arrayFilters: [] }, queryUpdates = { $set: {} };

for (const key in updates) {
    queryOptions.arrayFilters.push({ [`${key}`]: key });
    queryUpdates.$set[[`elem.$[${key}].value`]] = updates[key].newValue;
}
//update query
await doc.updateOne(queryUpdates, queryOptions);

//updated doc values:
{
    "id": 10,
    "list": [
        { id: 1, value: 10 },
        { id: 2, value: 20 }
     ]
}

Is there some way to do this directly in arrayFilters or some other method in MongoDB without having to make dynamic keys like the above?

1

There are 1 answers

0
Yong Shun On

You may work on the update with the aggregation pipeline.

  1. $let - Declare the variable(s) with updates array.

    1.1. $map - Iterate each element in the list array.

    1.1.1. $mergeObjects - Merge the current iterated element (ori) with the result from 1.1.1.1. If the result does exist, the current iterated element's fields will be updated. Otherwise, it remains as the original.

    1.1.1.1. $first & $filter - Get the first matching element from the newList by matching id.

await doc.updateOne({
  id: 10 // Update condition
},
[
  {
    $set: {
      list: {
        $let: {
          vars: {
            newList: updates
          },
          in: {
            $map: {
              input: "$list",
              as: "ori",
              in: {
                $mergeObjects: [
                  "$$ori",
                  {
                    $first: {
                      $filter: {
                        input: "$$newList",
                        cond: {
                          $eq: [
                            "$$this.id",
                            "$$ori.id"
                          ]
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      }
    }
  }
])

Note that the objects in the updates array are required to have the same field name as in your document in order to update (overwrite) the value.

Demo @ Mongo Playground