Bim Bam
Bim Bam

Reputation: 439

How to update a field using its previous value in MongoDB/Mongoose

For example, I have some documents that look like this:

{
    id: 1
    name: "foo"
}

And I want to append another string to the current name field value.

I tried the following using Mongoose, but it didn't work:

Model.findOneAndUpdate({ id: 1 }, { $set: { name: +"bar" } }, ...);

Upvotes: 6

Views: 18410

Answers (2)

Danziger
Danziger

Reputation: 21191

Edit:

From Compatibility Changes in MongoDB 3.6:

MongoDB 3.6.1 deprecates the snapshot query option.

For MMAPv1, use hint() on the { _id: 1} index instead to prevent a cursor from returning a document more than once if an intervening write operation results in a move of the document.

For other storage engines, use hint() with { $natural : 1 } instead.

Original 2017 answer:

You can't refer to the values of the document you want to update, so you will need one query to retrieve the document and another one to update it. It looks like there's a feature request for that in OPEN state since 2016.

If you have a collection with documents that look like:

{ "_id" : ObjectId("590a4aa8ff1809c94801ecd0"), "name" : "bar" }

Using the MongoDB shell, you can do something like this:

db.test.find({ name: "bar" }).snapshot().forEach((doc) => {
    doc.name = "foo-" + doc.name;

    db.test.save(doc);
});

The document will be updated as expected:

{ "_id" : ObjectId("590a4aa8ff1809c94801ecd0"), "name": "foo-bar" }

Note the .snapshot() call. This ensures that the query will not return a document multiple times because an intervening write operation moves it due to the growth in document size.

Applying this to your Mongoose example, as explained in this official example:

Cat.findById(1, (err, cat) => {
    if (err) return handleError(err);

    cat.name = cat.name + "bar";

    cat.save((err, updatedCat) => {
        if (err) return handleError(err);

        ...
    });
});

It's worth mentioning that there's a $concat operator in the aggregation framework, but unfortunately you can't use that in an update query.

Anyway, depending on what you need to do, you can use that together with the $out operator to save the results of the aggregation to a new collection.

With that same example, you will do:

db.test.aggregate([{
    $match: { name: "bar" }
}, {
    $project: { name: { $concat: ["foo", "-", "$name"] }}
}, {
    $out: "prefixedTest"
}]);

And a new collection prefixedTest will be created with documents that look like:

{ "_id" : ObjectId("XXX"), "name": "foo-bar" }

Just as a reference, there's another interesting question about this same topic with a few answers worth reading: Update MongoDB field using value of another field

Upvotes: 6

Evgin
Evgin

Reputation: 326

If this is still relevant, I have a solution for MongoDB 4.2.

I had the same problem where "projectDeadline" fields of my "project" documents were Array type (["2020","12","1"])

Using Robo3T, I connected to my MongoDB Atlas DB using SRV link. Then executed the following code and it worked for me.

Initial document:

{
  _id             : 'kjnolqnw.KANSasdasd',
  someKey         : 'someValue',
  projectDeadline : ['2020','12','1']
}

CLI Command:

db
.getCollection('mainData')
.find({projectDeadline: {$not: {$eq: "noDeadline"}}})
.forEach((doc) => {

    var deadline = doc.projectDeadline;
    var deadlineDate = new Date(deadline);

    db
    .mainData
    .updateOne({
        _id: doc._id}, 
        {"$set": 
            {"projectDeadline": deadlineDate}
        }
    )}
);

Resulting document:

{
  _id             : 'kjnolqnw.KANSasdasd',
  someKey         : 'someValue',
  projectDeadline : '2020-12-01 21:00:00.000Z'
}

Upvotes: 2

Related Questions