I have a situation almost, but not quite, entirely unlike this one. I have a collection to which I must assign new ids based on the old ones.
I thought of doing it using aggregation, adding a new field to preserve the old id in a new field aptly called "oldId", then setting the _id
to my new value (calculated by a function).
Then I'd like to merge that back into the collection (not all documents are adjusted in this matter — don't ask) but aye, there's the rub: I need to merge on _id
on one side, and oldId
on the other.
I need to merge the documents coming out of the pipeline into the existing collection on $$new.oldId === $_id
.
How do I do that?
I thought of using an aggregation pipeline in the whenMatched
of the merge, but it doesn't allow modification of the fields the merge was made on, which is exactly what I need to do.
I also thought of doing it using a regular updateMany
, but unfortunately
Uncaught MongoServerError: After applying the update, the (immutable) field '_id' was found to have been altered to _id: [redacted]
The solution I ended up using, which worked in my case, was dropping the $match
and modifying the transformation, to leave the id alone in some cases, then using $out
to replace the entire collection.
Full example of my current solution:
db.foo.insertMany([
{_id: 123, description: "This id needs to be changed to 369" },
{_id: 234, description: "This id needs to be left alone" },
{_id: 345, description: "This id needs to be left alone" }
]);
print("===========");
db.foo.find({});
print("===========");
db.foo.aggregate([
{$addFields: {
oldId: "$_id",
_id: { $function: {
body: function(id) {
if (id === 123) {
return id * 3;
} else {
return id;
}
},
args: ["$_id"],
lang: "js"
}}
}},
{$out: "foo"}
]);
db.foo.find({});
But I'd still like to know if I could've done better. I don't like modifying documents when I don't need to and I'd like to use the more fine-grained control that $merge
would've provided.
That would look something like this:
db.foo.aggregate([
{$match:{
_id: 123
}},
{$addFields: {
oldId: "$_id",
_id: { $function: {
body: function(id) {
return id * 3;
},
args: ["$_id"],
lang: "js"
}}
}},
{$merge: {
into: "foo",
on: "$$new.oldId === $_id", // this is what I'm trying to figure out
whenMatched: "replace",
whenNotMatched: "discard"
}}
]);