4

In MongoDB, I want to change the structure of my documents from:

{
    discount: 10,
    discountType: "AMOUNT"
}

to:

{
    discount: {
        value: 10,
        type: "AMOUNT"
    }
}

so I tried following query in mongo shell:

db.discounts.update({},
    {
        $rename: {
             discount: "discount.value",
             discountType: "discount.type"
        }
    },
    {multi: true}
)

but it throws an error:

"writeError" : {
    "code" : 2,
    "errmsg" : "The source and target field for $rename must not be on the same path: discount: \"discount.value\""
}

A workaround that comes to my mind is to do it in 2 steps: first assign the new structure to a new field (let's say discount2) and then rename it to discount. But maybe there is a way to do it one step?

styvane
  • 59,869
  • 19
  • 150
  • 156
Lukasz Wiktor
  • 19,644
  • 5
  • 69
  • 82

3 Answers3

5

The simplest way is to do it in two steps as you allude to in your question; initially renaming discount to a temporary field name so that it can be reused in the second step:

db.discounts.update({}, {$rename: {discount: 'temp'}}, {multi: true})
db.discounts.update({}, 
    {$rename: {temp: 'discount.value', discountType: 'discount.type'}},
    {multi: true})
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
1

The reason you are getting this error is because as mentioned in the documentation:

The $rename operator logically performs an $unset of both the old name and the new name, and then performs a $set operation with the new name. As such, the operation may not preserve the order of the fields in the document; i.e. the renamed field may move within the document.

And the problem with this is that you can't $set and $unset same field at the same time in MongoDB.

The solution will be to use bulk operations to update your documents in order to change their structure, and even in that case you need to use a field's name that doesn't exist in your collection. Of course the best way to do all this is using "Bulk" operations for maximum efficiency

MongoDB 3.2 or newer

MongoDB 3.2 deprecates Bulk() and its associated methods. You need to use the .bulkWrite() method.

var operations = [];
db.discounts.find().forEach(function(doc) {
    var discount = doc.discount; 
    var discountType = doc.discountType; 
    var operation = { 'updateOne': { 
        'filter': { '_id': doc._id }, 
        'update': { 
            '$unset': { 'discount': '', 'discountType': '' }, 
            '$set': { 'discounts.value': discount, 'discounts.type': discountType }
        }
    }};
    operations.push(operation); 
});

operations.push( {
    ordered: true,      
    writeConcern: { w: "majority", wtimeout: 5000 } 
});

db.discounts.bulkWrite(operations);

Which yields:

{
        "_id" : ObjectId("56682a02e6a2321d88f6d078"),
        "discounts" : {
                "value" : 10,
                "type" : "AMOUNT"
        }
}

MongoDB 2.6

Prior to MongoDB 3.2 and using MongoDB version 2.6 or newer you can use the "Bulk" API.

var  bulk = db.discounts.initializeOrderedBulkOp();
var count = 0;
db.discounts.find().forEach(function(doc) { 
    var discount = doc.discount; 
    var discountType = doc.discountType; 
    bulk.find( { '_id': doc._id } ).updateOne( {
        '$unset': { 'discount': '', 'discountType': '' }, 
        '$set': { 'discounts.value': discount, 'discounts.type': discountType }  }); 
   count++; 
   if (count % 500 === 0) {
       bulk.execute();
       bulk = db.discounts.initializeOrderedBulkOp(); 
    } 
})

if (count > 0)   
    bulk.execute();

This query yields same result as previous one.

Community
  • 1
  • 1
styvane
  • 59,869
  • 19
  • 150
  • 156
0

Thanks to answers from Update MongoDB field using value of another field I figured out following solution:

db.discounts.find().snapshot().forEach(
    function(elem) {
        elem.discount = {
            value: elem.discount,
            type: elem.discountType
        }
        delete elem.discountType;
        db.discounts.save(elem);
    }
)

Which I quite like because the source code reads nicely but performance sucks for large amount of documents.

Community
  • 1
  • 1
Lukasz Wiktor
  • 19,644
  • 5
  • 69
  • 82