简体   繁体   中英

MongoDB update multiple items in an array of objects with corresponding data

I have an array in my MongoDB document as shown below:

{
  ...otherDocFields,
  groupID: "group-id",
  users: [
    {id: "uid1", name: "User1"},
    {id: "uid2", name: "User2"},
    {id: "uid3", name: "User3"},
    {id: "uid4", name: "User4"}
  ]
}

I'm trying to write a function that will update users' names based on that ID.

I tried something like:

async function updateNames(groupID: string, data: Array<{id: string, name: string}>) {
  try {
    // MongoDB Aggregation 
    await mongoDB.collection("users").aggregate([
      {$match: {groupID}},
      {$unwind: {
          path: '$users', 
          includeArrayIndex: 'users.id', 
          preserveNullAndEmptyArrays: true
        }
      }
      //....
    ])
  } catch (e) {
    console.log(e)
  }
}

I'm stuck at the part to update the relevant names from the data param in the function. A sample data would be:

[
  {id: "uid1", name: "newName1"},
  {id: "uid3", name: "newName3"}
]

I can for sure read, manually process it and update the document but I'm looking for a way to do it in single go using aggregation.

You can do this with an update statement using array filters ( https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#---identifier-- )

First, if we just declare some tests data which would be passed into your method:

const data = [
    {id: "uid2", name: "ChangeName1"},
    {id: "uid4", name: "ChangedName2"}
];

We can then create an update statement which updates all the users within that list within a map and a reduce:

const sets = data.map((element, index) => ({ [`users.$[f${index}].name`]: element.name })).reduce((accumulator, currentValue) => (Object.assign(currentValue, accumulator)), { });
const update = { $set: sets };

This will give us the following update statement:

{
        "$set" : {
                "users.$[f1].name" : "ChangedName2",
                "users.$[f0].name" : "ChangeName1"
        }
}

We can create a bunch of array filters with that data:

const arrayFilters = data.map((element, index) => ({ [`f${index}.id`]: element.id }));

This will give us the following which we can pass into the options of an update.

[ { "f0.id" : "uid2" }, { "f1.id" : "uid4" } ]

Last of all we can execute the following update command:

db.users.updateOne(
    filter,
    update,
    { arrayFilters }
);

Now if we check the output we'll get the following results

> db.users.find().pretty()
{
        "_id" : ObjectId("60e20841351156603932c526"),
        "groupID" : "123",
        "users" : [
                {
                        "id" : "uid1",
                        "name" : "User1"
                },
                {
                        "id" : "uid2",
                        "name" : "ChangeName1"
                },
                {
                        "id" : "uid3",
                        "name" : "User3"
                },
                {
                        "id" : "uid4",
                        "name" : "ChangedName2"
                }
        ]
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM