552

In MongoDB, is it possible to update the value of a field using the value from another field? The equivalent SQL would be something like:

UPDATE Person SET Name = FirstName + ' ' + LastName

And the MongoDB pseudo-code would be:

db.person.update( {}, { $set : { name : firstName + ' ' + lastName } );
Sede's user avatar
Sede
61.5k20 gold badges158 silver badges162 bronze badges
askedOct 20, 2010 at 5:22
Chris Fulstow's user avatar
1
  • the braces don't match... if i get it to work, i will correct itCommentedFeb 1, 2023 at 16:03

13 Answers13

576
+50

The best way to do this is in version 4.2+ which allows using the aggregation pipeline in theupdate document and theupdateOne,updateMany, orupdate(deprecated in most if not all languages drivers) collection methods.

MongoDB 4.2+

Version 4.2 also introduced the$set pipeline stage operator, which is an alias for$addFields. I will use$set here as itmaps with what we are trying to achieve.

db.collection.<update method>(    {},    [        {"$set": {"name": { "$concat": ["$firstName", " ", "$lastName"]}}}    ])

Note that square brackets in the second argument to the method specify an aggregation pipeline instead of a plain update document because using a simple document willnot work correctly.

MongoDB 3.4+

In 3.4+, you can use$addFields and the$out aggregation pipeline operators.

db.collection.aggregate(    [        { "$addFields": {             "name": { "$concat": [ "$firstName", " ", "$lastName" ] }         }},        { "$out": <output collection name> }    ])

Note that thisdoes not update your collection but instead replaces the existing collection or creates a new one. Also, for update operations that require"typecasting", you will need client-side processing, and depending on the operation, you may need to use thefind() method instead of the.aggreate() method.

MongoDB 3.2 and 3.0

The way we do this is by$projecting our documents and using the$concat string aggregation operator to return the concatenated string.You then iterate thecursor and use the$set update operator to add the new field to your documents usingbulk operations for maximum efficiency.

Aggregation query:

var cursor = db.collection.aggregate([     { "$project":  {         "name": { "$concat": [ "$firstName", " ", "$lastName" ] }     }}])

MongoDB 3.2 or newer

You need to use thebulkWrite method.

var requests = [];cursor.forEach(document => {     requests.push( {         'updateOne': {            'filter': { '_id': document._id },            'update': { '$set': { 'name': document.name } }        }    });    if (requests.length === 500) {        //Execute per 500 operations and re-init        db.collection.bulkWrite(requests);        requests = [];    }});if(requests.length > 0) {     db.collection.bulkWrite(requests);}

MongoDB 2.6 and 3.0

From this version, you need to use the now deprecatedBulk API and itsassociated methods.

var bulk = db.collection.initializeUnorderedBulkOp();var count = 0;cursor.snapshot().forEach(function(document) {     bulk.find({ '_id': document._id }).updateOne( {        '$set': { 'name': document.name }    });    count++;    if(count%500 === 0) {        // Excecute per 500 operations and re-init        bulk.execute();        bulk = db.collection.initializeUnorderedBulkOp();    }})// clean up queuesif(count > 0) {    bulk.execute();}

MongoDB 2.4

cursor["result"].forEach(function(document) {    db.collection.update(        { "_id": document._id },         { "$set": { "name": document.name } }    );})
answeredMay 17, 2016 at 15:27
Sede's user avatar
Sign up to request clarification or add additional context in comments.

19 Comments

4.2+ Doesn't work. MongoError: The dollar ($) prefixed field '$concat' in 'name.$concat' is not valid for storage.
@JoshWoodcock, I think you had a typo in the query you are running. I suggest you double check.
For those running into the same problem @JoshWoodcock described: pay attention that the answer for 4.2+ describes anaggregation pipeline, so don't miss thesquare brackets in the second parameter!
Is it possible to do the same thing as in this solution but instead of concatenating two strings together, add two numbers together?
How many times are they going to change this before it becomes a joke?
|
264

You should iterate through. For your specific case:

db.person.find().snapshot().forEach(    function (elem) {        db.person.update(            {                _id: elem._id            },            {                $set: {                    name: elem.firstname + ' ' + elem.lastname                }            }        );    });
evandrix's user avatar
evandrix
6,2464 gold badges30 silver badges38 bronze badges
answeredJan 20, 2013 at 9:17
Carlos Morales's user avatar

12 Comments

What happens if another user has changed the document between your find() and your save()?
True, but copying between fields should not require transactions to be atomic.
It's important to notice thatsave() fully replaces the document. Should useupdate() instead.
How aboutdb.person.update( { _id: elem._id }, { $set: { name: elem.firstname + ' ' + elem.lastname } } );
I created a function calledcreate_guid that only produced a unique guid per document when iterating withforEach in this way (i.e. simply usingcreate_guid in anupdate statement withmutli=true caused the same guid to be generated for all documents). This answer worked perfectly for me. +1
|
108

Apparently there is a way to do this efficiently since MongoDB 3.4, seestyvane's answer.


Obsolete answer below

You cannot refer to the document itself in an update (yet). You'll need to iterate through the documents and update each document using a function. Seethis answer for an example, orthis one for server-sideeval().

answeredOct 20, 2010 at 9:03
Niels van der Rest's user avatar

5 Comments

Is this still valid today?
@ChristianEngel: It appears so. I wasn't able to find anything in the MongoDB docs that mentions a reference to the current document in anupdate operation.This related feature request is still unresolved as well.
Is it still valid in April 2017? Or there are already new features which can do this?
@Kim It looks like it is still valid. Also, thefeature request that @niels-van-der-rest pointed out back in 2013 is still inOPEN.
this is not a valid answer anymore, have a look at @styvane answer
49

For a database with high activity, you may run into issues where your updates affect actively changing records and for this reason I recommend usingsnapshot()

db.person.find().snapshot().forEach( function (hombre) {    hombre.name = hombre.firstName + ' ' + hombre.lastName;     db.person.save(hombre); });

http://docs.mongodb.org/manual/reference/method/cursor.snapshot/

answeredFeb 11, 2015 at 16:58
Eric Kigathi's user avatar

3 Comments

What happens if another user edited the person between the find() and save()? I have a case where multiple calls can be done to the same object changing them based on their current values. The 2nd user should have to wait with reading until the 1st is done with saving. Does this accomplish that?
About thesnapshot():Deprecated in the mongo Shell since v3.2. Starting in v3.2, the $snapshot operator is deprecated in the mongo shell. In the mongo shell, use cursor.snapshot() instead.link
Nice method. It might be very slow looping through thousands or tens of thousands of objects.
44

StartingMongo 4.2,db.collection.update() can accept an aggregation pipeline, finally allowing the update/creation of a field based on another field:

// { firstName: "Hello", lastName: "World" }db.collection.updateMany(  {},  [{ $set: { name: { $concat: [ "$firstName", " ", "$lastName" ] } } }])// { "firstName" : "Hello", "lastName" : "World", "name" : "Hello World" }
  • The first part{} is the match query, filtering which documents to update (in our case all documents).

  • The second part[{ $set: { name: { ... } }] is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline).$set is a new aggregation operator and an alias of$addFields.

answeredJun 11, 2019 at 20:35
Xavier Guihot's user avatar

4 Comments

Worked for me. Was assigning one field to another without concatenation and it worked. Thank you!
what is the difference between your first point and your third? {} mean all doc then why { multi: true }
@Coder17 the first part{} is a filtering part: for instance you might want to updatedoc1 anddoc2 but notdoc3. Without the third part, by default, the update will be applied only on 1 document for instancedoc1, anddoc2 wouldn't be updated. Note that you can also usedb.collection.updateMany to get rid of the third parameter.
at this point we'd use updateMany
18

Regarding thisanswer, the snapshot function is deprecated in version 3.6, according to thisupdate. So, on version 3.6 and above, it is possible to perform the operation this way:

db.person.find().forEach(    function (elem) {        db.person.update(            {                _id: elem._id            },            {                $set: {                    name: elem.firstname + ' ' + elem.lastname                }            }        );    });
answeredJan 15, 2019 at 17:20
Aldo's user avatar

Comments

10

update() method takes aggregation pipeline as parameter like

db.collection_name.update(  {    // Query  },  [    // Aggregation pipeline    { "$set": { "id": "$_id" } }  ],  {    // Options    "multi": true // false when a single doc has to be updated  })

The field can be set or unset with existing values using the aggregation pipeline.

Note: use$ with field name to specify the field which has to be read.

answeredJul 13, 2021 at 13:01
Yuvaraj Anbarasan's user avatar

2 Comments

Only works MongoDB 4.2 and later. See:stackoverflow.com/a/37280419/404699
Thanks for pointing it out. The solution works for MongoDb 4.2+ versions
8

I tried the above solution but I found it unsuitable for large amounts of data. I then discovered the stream feature:

MongoClient.connect("...", function(err, db){    var c = db.collection('yourCollection');    var s = c.find({/* your query */}).stream();    s.on('data', function(doc){        c.update({_id: doc._id}, {$set: {name : doc.firstName + ' ' + doc.lastName}}, function(err, result) { /* result == true? */} }    });    s.on('end', function(){        // stream can end before all your updates do if you have a lot    })})
answeredApr 3, 2015 at 11:44
Chris Gibb's user avatar

1 Comment

How is this different? Will the steam be throttled by the update activity? Do you have any reference to it? The Mongo docs are quite poor.
2

Here's what we came up with for copying one field to another for ~150_000 records. It took about 6 minutes, but is still significantly less resource intensive than it would have been to instantiate and iterate over the same number of ruby objects.

js_query = %({  $or : [    {      'settings.mobile_notifications' : { $exists : false },      'settings.mobile_admin_notifications' : { $exists : false }    }  ]})js_for_each = %(function(user) {  if (!user.settings.hasOwnProperty('mobile_notifications')) {    user.settings.mobile_notifications = user.settings.email_notifications;  }  if (!user.settings.hasOwnProperty('mobile_admin_notifications')) {    user.settings.mobile_admin_notifications = user.settings.email_admin_notifications;  }  db.users.save(user);})js = "db.users.find(#{js_query}).forEach(#{js_for_each});"Mongoid::Sessions.default.command('$eval' => js)
answeredJun 8, 2016 at 15:07
Chris Bloom's user avatar

Comments

0

WithMongoDB version 4.2+, updates are more flexible as it allows the use of aggregation pipeline in itsupdate,updateOne andupdateMany. You can now transform your documents using the aggregation operators then update without the need to explicity state the$set command (instead we use$replaceRoot: {newRoot: "$$ROOT"})

Here we use the aggregate query to extract the timestamp from MongoDB's ObjectID "_id" field and update the documents (I am not an expert in SQL but I think SQL does not provide any auto generated ObjectID that has timestamp to it, you would have to automatically create that date)

var collection = "person"agg_query = [    {        "$addFields" : {            "_last_updated" : {                "$toDate" : "$_id"            }        }    },    {        $replaceRoot: {            newRoot: "$$ROOT"        }     }]db.getCollection(collection).updateMany({}, agg_query, {upsert: true})
answeredApr 15, 2020 at 11:06
Yi Xiang Chong's user avatar

2 Comments

You don't need{ $replaceRoot: { newRoot: "$$ROOT" } }; it means replacing the document by itself, which is pointless. If you replace$addFields by its alias$set andupdateMany which is one of the aliases forupdate, then you get to the exact same answer asthis one above.
Thanks @Xavier Guihot for the comment. The reason why$replaceRoot is used because we needed to use the$toDate query, while$set does not provide us a way to use$toDate
0

(I would have posted this as a comment, but couldn't)

For anyone who lands here trying to update one field using another in the document with the c# driver...I could not figure out how to use any of theUpdateXXX methods and their associated overloads since they take anUpdateDefinition as an argument.

// we want to set Prop1 to Prop2class Foo { public string Prop1 { get; set; } public string Prop2 { get; set;} } void Test(){      var update = new UpdateDefinitionBuilder<Foo>();     update.Set(x => x.Prop1, <new value; no way to get a hold of the object that I can find>)}

As a workaround, I found that you can use theRunCommand method on anIMongoDatabase (https://docs.mongodb.com/manual/reference/command/update/#dbcmd.update).

var command = new BsonDocument        {            { "update", "CollectionToUpdate" },            { "updates", new BsonArray                  {                        new BsonDocument                       {                            // Any filter; here the check is if Prop1 does not exist                            { "q", new BsonDocument{ ["Prop1"] = new BsonDocument("$exists", false) }},                             // set it to the value of Prop2                            { "u", new BsonArray { new BsonDocument { ["$set"] = new BsonDocument("Prop1", "$Prop2") }}},                            { "multi", true }                       }                 }            }        }; database.RunCommand<BsonDocument>(command);
answeredAug 23, 2020 at 22:07
user1239961's user avatar

Comments

0

MongoDB 4.2+ Golang

result, err := collection.UpdateMany(ctx, bson.M{},    mongo.Pipeline{        bson.D{{"$set",          bson.M{"name": bson.M{"$concat": []string{"$lastName", " ", "$firstName"}}}    }},)
answeredDec 7, 2022 at 15:09
igorushi's user avatar

Comments

0

If you need just copy original field value:

db.collection.updateMany({},[    {"$set": {"field_2": { "$concat": ["$field_1", ""]}}}]

)

answeredNov 19, 2024 at 11:04
Sergei Dokshin's user avatar

1 Comment

Thank you for your interest in contributing to the Stack Overflow community. This question already has quite a few answers—including one that has been extensively validated by the community. Are you certain your approach hasn’t been given previously?If so, it would be useful to explain how your approach is different, under what circumstances your approach might be preferred, and/or why you think the previous answers aren’t sufficient. Can you kindlyedit your answer to offer an explanation?

Your Answer

Sign up orlog in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

By clicking “Post Your Answer”, you agree to ourterms of service and acknowledge you have read ourprivacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.