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?

1

3 Answers 3

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)

Sign up to request clarification or add additional context in comments.

13 Comments

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()
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.
There's no atomic way of doing this? This solution doesn't seem very robust.
Is this still true with mongodb 3.0?
@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.
|
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();
}

2 Comments

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?
aggregate with out is not sutable, if match needed: final collection will contain only matched documents.
-1

This can be done through:

db.nameOfCollection.find().forEach(
    function (elem) {
        db.nameOfCollection.update(
            {
                _id: elem._id
            },
            {
                $set: {
                    field2: elem.field1
                }
            }
        );
    }
);

2 Comments

You failed your copy-paste of stackoverflow.com/a/14423151/2054072
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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.