29

I have a collection t1 with the following fields in its schema

_id, field1, field1

I want set field2's value field1 like sql:

update t1 set field1=field2;

How do I do it in MongoDB?

styvane
  • 59,869
  • 19
  • 150
  • 156
firefox01
  • 291
  • 1
  • 3
  • 4

3 Answers3

46

Good and bad news here.

Bad news is that AFAIK you can't do it with a single update() call - mongo doesn't support referring to current object in update.

Good news is that there are other ways to do it, e.g. you can run a forEach loop:

db.item.find(conditions...).snapshot().forEach( function (doc) {
  doc.field1 = doc.field2; 
  db.item.save(doc); 
});

You can run forEach in the admin shell ('mongo' command), or through some of the methods of your specific driver (e.g. in PHP I'd expect it to work with mongodb.execute() as described in here: http://www.php.net/manual/en/mongodb.execute.php)

Anthony O.
  • 22,041
  • 18
  • 107
  • 163
Jakub P.
  • 5,416
  • 2
  • 21
  • 21
  • 15
    Because a call to save() is being made within the forEach loop, the cursor will get messed up and the function will potentially get called multiple times for each document. The solution is to call snapshot() before the foreach: db.item.find(blah).snapshot().forEach() – Joel Harris Aug 03 '12 at 19:03
  • Thanks for that. I was not aware of the snapshot() requirement for this to work as intended. TBH I was taken aback at first that a function called "forEach" doesn't guarantee what its name is implying (because it's similar to what you can find in collections libraries in programming languages), but then again, I understand that there may be plenty of reasons to do that. – Jakub P. Sep 03 '12 at 11:43
  • 3
    There's no atomic way of doing this? This solution doesn't seem very robust. – UpTheCreek Feb 15 '13 at 11:23
  • @JakubP.: If i have 10 fields in my document, and i have to update only a single field, should i use save() for this...the updated value will be calculated from old value – Shashank Nov 13 '13 at 08:29
  • if you have enough space on your hard disk, consider saving to a new collection, then drop and rename. Dropping and renaming was pretty fast. – tacone Feb 10 '14 at 20:32
  • 1
    Is this still true with mongodb 3.0? – heinob Apr 25 '15 at 03:24
  • Use caution with this approach when used in conjunction with a projection. Specifically, the updated document is saved with only the fields that where part of the projection. – BrentR Feb 23 '16 at 19:30
  • @tacone You can only do the "drop and rename" strategy if you have some way to guarantee you don't have new data coming in during the interim, or if you have a way to sync that data after the fact in the "good" form. – Merlyn Morgan-Graham Apr 20 '16 at 01:50
  • @MerlynMorgan-Graham of course and the same applies to all the answers in this page. MongoDB does not have transactions and atomicity is guaranteed only at single document level. – tacone Apr 20 '16 at 07:59
  • 1
    @tacone True. If you have an advantage of running on downtime, both solutions give you similar results, but drop-and-rename might be simpler to know that you're "done". If you have a hot sync, then doing it in-place might be a simpler solution, cause you might be able to fix the data source, then loop until it runs clean. Maybe. I haven't done the hot-sync in production before :) Just wanted to point out the syncing issue, really. – Merlyn Morgan-Graham Apr 20 '16 at 08:07
  • @JoelHarris maybe you or OP knows, but really anyone reading this: Did snapshot() get deprecated at some point. I got an error, and I can't find any mention of it in the 4.0 documentation, but I also don't see any mention of its deprecation in release notes. – Albert Rothman Aug 07 '18 at 21:11
  • Comparing the 3.2 documentation to the 4.0 documentation, it seems 4.0 does not use snapshot, and instead you must specify an index using hint()...I am brand new to mongo, so I don't want to spread misinformation, but I am curious to know if this is accurate. https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#cursor-snapshot – Albert Rothman Aug 07 '18 at 21:22
  • For those reading this in year >= 2022: .snapshot() method is not in mongodb API anymore. But you can use .toArray() instead. – it-alien Jan 14 '22 at 14:51
14

Starting from version 3.4, we can use the $addFields aggregation pipeline operator to this without client side processing which is the most efficient way.

db.collection.aggregate(
    [
        { "$addFields": { "field2": "$field1" }},
        { "$out": "collection" }
    ]
)

Prior to version 3.4 we need to iterate the Cursor object and use $set operator to add the new field with the existing "field1" value. You need to do this using "bulk" operation for maximum efficiency.

MongoDB 3.2 deprecates Bulk() and its associated methods, thus from 3.2 upwards you need to use the bulkWrite method.

var requests = [];
db.collection.find({}, { 'field1': 1 } ).snapshot().forEach(document => { 
    requests.push( { 
        'updateOne': {
            'filter': { '_id': document._id },
            'update': { '$set': { 'field2': document.field1 } }
        }
    });
    if (requests.length === 1000) {
        //Execute per 1000 operations and re-init
        db.collection.bulkWrite(requests);
        requests = [];
    }
});

if(requests.length > 0) {
    db.collection.bulkWrite(requests);
}

From version 2.6 to 3.0 you can use the Bulk API.

var bulk = db.collection.initializeUnorderedBulOp();
var count = 0;

db.collection.find({}, { 'field1': 1 }).snapshot().forEach(function(document) { 
    bulk.find({ '_id': document._id }).updateOne( {
        '$set': { 'field2': document.field1 }
    });
    count++;
    if(count%1000 === 0) {
        // Excecute per 1000 operations and re-init
        bulk.execute();
        bulk = db.collection.initializeUnorderedBulkOp();
    }
})

// clean up queues
if(count > 0) {
    bulk.execute();
}
styvane
  • 59,869
  • 19
  • 150
  • 156
  • In my tests, I found the aggregation method to be MUCH faster. My sample data had 150K documents that took 5 seconds to reshape, whereas the .foreach methods took ~1.5 minutes. What are caveats/gotchas to be considered when going with the aggregation approach? – Tung Dec 21 '17 at 09:05
  • 2
    `aggregate` with `out` is not sutable, if `match` needed: final collection will contain only matched documents. – AstraSerg Mar 23 '18 at 04:06
-1

This can be done through:

db.nameOfCollection.find().forEach(
    function (elem) {
        db.nameOfCollection.update(
            {
                _id: elem._id
            },
            {
                $set: {
                    field2: elem.field1
                }
            }
        );
    }
);
CTarczon
  • 898
  • 9
  • 19
Daljeet Singh
  • 704
  • 3
  • 7
  • 17
  • 6
    You failed your copy-paste of http://stackoverflow.com/a/14423151/2054072 – Preview Sep 08 '15 at 17:29
  • yes, @Deejay if you find an answer somewhere else in StackOverflow, please refer a link instead, that will help us in participating cross-questioning with the teacher himself/herself. – Vijay Rajpurohit Aug 06 '19 at 07:32