updateMany: Replace string in array field, ignore duplicates

Hello there,

I need to replace a string in an array field on multiple docs without creating duplicate values in the array.

Example documents:

{
  _id: ObjectId('6284ea2cad10a6a1b43a7f53'),
  bookingnr: 'BA-34567-3333-92540',
  flags: ['direct', 'last-minute', 'family']
}
{
  _id: ObjectId("628614db8e29fc2a5c8e4f0c"),
  bookingnr: 'TA-87965-48521-89809',
  flags: ['direct', 'reduced', 'external-booking', 'in-house']
}

What i came up with for now is:

db.collection.updateMany(
  { flags: 'direct' },
  { $set: { 'flags.$': 'in-house' } }
);

But this creates duplicate values when the value used in $set is already part of the array. So how can I achieve this without duplicate array entries?

Further details/requirements:

  • The solution should NOT throw an error on duplicates. Instead it should just not add it.
  • I use mongoose for schemas, therefore a solution using pre/post hooks would also work for me.

cheers

See $addToSet.

Thanks, I already looked into it, but $addToSet alone would not remove the one I want to replace. And I can’t figure out how to do both at the same time. :confused:

I tried:

db.collection.updateMany(
  { flags: 'direct' },
  { 
    $pull: { flags: 'direct' } ,
    $addToSet: { flags:  'in-house' }
  }
);

which gives this error:

MongoServerError: Updating the path 'flags' would create a conflict at 'flags'

Somewhere i read that this means I can’t modify the same field multiple times in one update query.

And I tried using an aggregation pipeline instead, but I don’t unterstand how to use $addToSet in there, because as described in the docs the only stages accepted are $addFields/$set $project/$unset $replaceRoot/$replaceWith.

This does not work:

db.collection.updateMany(
  { flags: 'direct' },
  [ 
    { $set: { 'flags.$': 'in-house' } },
    { $addToSet: { flags:  'in-house' } }
  ]
);

What do you want to do with flags:direct when flags: in-house is already present?

Using aggregation update:

To remove direct you may $filter flags array.

Then use $reduce to determine if in-house is present or not, then adding it or not.