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 + ' ' + LastNameAnd the MongoDB pseudo-code would be:
db.person.update( {}, { $set : { name : firstName + ' ' + lastName } );- the braces don't match... if i get it to work, i will correct itwojand– wojand2023-02-01 16:03:48 +00:00CommentedFeb 1, 2023 at 16:03
13 Answers13
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 } } );})19 Comments
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 } } ); });12 Comments
save() fully replaces the document. Should useupdate() instead.db.person.update( { _id: elem._id }, { $set: { name: elem.firstname + ' ' + elem.lastname } } );create_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. +1Apparently 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().
5 Comments
update operation.This related feature request is still unresolved as well.OPEN.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/
3 Comments
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).$setis a new aggregation operator and an alias of$addFields.
4 Comments
{} 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.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 } } ); });Comments
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.
2 Comments
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 })})1 Comment
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)Comments
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})2 Comments
{ $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.$replaceRoot is used because we needed to use the$toDate query, while$set does not provide us a way to use$toDate(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);Comments
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"}}} }},)Comments
If you need just copy original field value:
db.collection.updateMany({},[ {"$set": {"field_2": { "$concat": ["$field_1", ""]}}}])
1 Comment
Explore related questions
See similar questions with these tags.









