Actualizar el campo MongoDB usando el valor de otro campo

En MongoDB, ¿es posible actualizar el valor de un campo usando el valor de otro campo? El SQL equivalente sería algo así como:

UPDATE Person SET Name = FirstName + ' ' + LastName 

Y el pseudocódigo MongoDB sería:

 db.person.update( {}, { $set : { name : firstName + ' ' + lastName } ); 

No puede consultar el documento en sí en una actualización (todavía). Tendrá que recorrer los documentos y actualizar cada documento con una función. Vea esta respuesta para un ejemplo, o este para la eval() lado del servidor eval() .

Deberías iterar a través de. Para su caso específico:

 db.person.find().snapshot().forEach( function (elem) { db.person.update( { _id: elem._id }, { $set: { name: elem.firstname + ' ' + elem.lastname } } ); } ); 

La mejor manera de hacerlo es usar el marco de agregación para calcular nuestro nuevo campo.

MongoDB 3.4

La solución más eficiente está en MongoDB 3.4 utilizando los operadores de canalización $addFields y $out aggregation.

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

Tenga en cuenta que esto no actualiza su colección, sino que reemplaza la colección existente o crea una nueva. Además, para las operaciones de actualización que requieren “conversión de tipo”, necesitará un procesamiento del lado del cliente, y dependiendo de la operación, es posible que necesite usar el método find() lugar del método .aggreate() .

MongoDB 3.2 y 3.0

La forma en que hacemos esto es mediante la $project de nuestros documentos y usamos el operador de agregación de cadenas $concat para devolver la cadena concatenada. Desde allí, puede iterar el cursor y usar el operador de actualización $set para agregar el nuevo campo a sus documentos usando operaciones masivas para una máxima eficiencia.

Consulta de agregación:

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

MongoDB 3.2 o posterior

a partir de esto, debe usar el método bulkWrite .

 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 y 3.0

A partir de esta versión, debe usar la API Bulk ahora en desuso y sus métodos asociados .

 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 queues if(count > 0) { bulk.execute(); } 

MongoDB 2.4

 cursor["result"].forEach(function(document) { db.collection.update( { "_id": document._id }, { "$set": { "name": document.name } } ); }) 

Para una base de datos con alta actividad, puede encontrarse con problemas en los que las actualizaciones afectan los registros que cambian activamente y por esta razón, recomiendo usar snapshot ()

 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/

Probé la solución anterior, pero me pareció inadecuada para grandes cantidades de datos. Luego descubrí la función de transmisión:

 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 }) }) 

Esto es lo que se nos ocurrió para copiar un campo en otro para ~ 150_000 registros. Tardó unos 6 minutos, pero sigue siendo significativamente menos intensivo en recursos de lo que hubiera sido crear instancias e iterar sobre la misma cantidad de objetos de Ruby.

 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)