Aggiorna il campo MongoDB usando il valore di un altro campo

In MongoDB, è ansible aggiornare il valore di un campo usando il valore di un altro campo? L’equivalente SQL sarebbe qualcosa di simile:

UPDATE Person SET Name = FirstName + ' ' + LastName 

E lo pseudo-codice MongoDB sarebbe:

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

Non è ansible fare riferimento al documento stesso in un aggiornamento (ancora). Avrai bisogno di scorrere i documenti e aggiornare ogni documento usando una funzione. Vedere questa risposta per un esempio o questa per l’ eval() lato server.

Dovresti scorrere. Per il tuo caso specifico:

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

Il modo migliore per farlo è utilizzare la struttura di aggregazione per calcolare il nostro nuovo campo.

MongoDB 3.4

La soluzione più efficiente è in MongoDB 3.4 utilizzando $addFields e gli operatori di pipeline di aggregazione $out .

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

Nota che questo non aggiorna la tua collezione ma sostituisce la collezione esistente o ne crea una nuova. Anche per le operazioni di aggiornamento che richiedono “type casting” è necessario l’elaborazione lato client e , a seconda dell’operazione, potrebbe essere necessario utilizzare il metodo find() anziché il metodo .aggreate() .

MongoDB 3.2 e 3.0

Il modo in cui lo facciamo è di $project i nostri documenti e utilizzare l’operatore di aggregazione di stringhe $concat per restituire la stringa concatenata. Da lì, quindi, iterare il cursore e utilizzare l’operatore di aggiornamento $set per aggiungere il nuovo campo ai tuoi documenti utilizzando le operazioni di massa per la massima efficienza.

Query di aggregazione:

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

MongoDB 3.2 o successivo

da questo, è necessario utilizzare il metodo 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 e 3.0

Da questa versione è necessario utilizzare l’API Bulk ora obsoleta e i relativi metodi associati .

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

Per un database con un’attività elevata, è ansible che si verifichino problemi in cui gli aggiornamenti influiscono triggersmente sui record e per questo motivo raccomando l’uso di 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/

Ho provato la soluzione di cui sopra ma l’ho trovata inadatta per grandi quantità di dati. Ho quindi scoperto la funzione di streaming:

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

Ecco cosa abbiamo pensato per copiare un campo in un altro per ~ 150.000 record. Ci sono voluti circa 6 minuti, ma è ancora significativamente meno dispendioso di risorse di quanto sarebbe stato per istanziare e iterare sullo stesso numero di oggetti 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)