Pregunta

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

UPDATE Person SET Name = FirstName + ' ' + LastName

Y el pseudo-código MongoDB sería:

db.person.update( {}, { $set : { name : firstName + ' ' + lastName } );
¿Fue útil?

Solución

La mejor manera de hacer esto es en la versión 4.2 y versiones posteriores, que permite el uso de tubería de agregación en el documento de actualización y la updateOne , updateMany o update método de recolección. Tenga en cuenta que este último ya no se utiliza en la mayoría si no todos los conductores idiomas.

MongoDB 4,2 +

Version 4.2 también introdujo el href="https://docs.mongodb.com/master/reference/operator/aggregation/set/" operador rel="nofollow noreferrer"> $set etapa de canalización $addFields . Voy a utilizar $set aquí, ya que mapas con lo que estamos tratando de lograr.

db.collection.<update method>(
    {},
    [
        {"$set": {"name": { "$concat": ["$firstName", " ", "$lastName"]}}}
    ]
)

MongoDB 3,4 +

En 3.4+ puede utilizar $addFields y la $out operadores de tuberías agregación.

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

Tenga en cuenta que este no actualiza su colección sino que reemplazar la colección existente o crear una nueva. También para las operaciones de actualización que requieren "conversión de tipo" tendrá el procesamiento del lado del cliente, y, dependiendo de la operación, es posible que tenga que utilizar el método find() en lugar del método .aggreate().

MongoDB 3.2 y 3.0

La forma de hacer esto es mediante la $project ing nuestros documentos y utilizar el operador $concat agregación cadena a devolver la cadena concatenada. A partir de ahí que, a continuación, iterar el cursor y el uso de la $set actualización para agregar el nuevo campo a sus documentos utilizando operaciones masivas para una máxima eficiencia.

Agregación de consulta:

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

MongoDB 3.2 o posterior

de esto, es necesario utilizar el bulkWrite método.

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 se necesita usar el obsoleto Bulk API y su 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 } }
    );
})

Otros consejos

Se debe recorrer. Para su caso específico:

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

Al parecer hay una manera de hacer esto de manera eficiente desde MongoDB 3.4, consulte la respuesta .


Respuesta obsoleto a continuación

No se puede hacer referencia al documento en sí mismo en una actualización (todavía). Tendrá que repetir por los documentos y actualizar cada documento utilizando una función. Ver esta respuesta para un ejemplo o éste para eval() del lado del servidor.

Para una base de datos con alta actividad, es posible que tenga problemas en sus cambios afectan a los registros que cambian de forma activa y por esta razón por la que recomendamos el uso de instantánea ()

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/

Me trató la solución anterior, pero me pareció que no sea adecuado para grandes cantidades de datos. Entonces descubrí la característica de corriente:

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

Con respecto a este respuesta , la función instantánea está en desuso en la versión 3.6, de acuerdo con esta actualización . Así, en la versión 3.6 y superiores, es posible realizar la operación de esta manera:

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

Esto es lo que se nos ocurrió para la copia de un campo a otro para ~ 150_000 registros. Tomó cerca de 6 minutos, pero sigue siendo significativamente menos recursos de lo que hubiera sido para crear instancias y iterar sobre el mismo número de objetos rubí.

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)

A partir Mongo 4.2, db.collection.update() puede aceptar una tubería de agregación, permitiendo finalmente la actualización / creación de un campo basado en otro campo:

// { firstName: "Hello", lastName: "World" }
db.collection.update(
  {},
  [{ $set: { name: { $concat: [ "$firstName", " ", "$lastName" ] } } }],
  { multi: true }
)
// { "firstName" : "Hello", "lastName" : "World", "name" : "Hello World" }
  • La primera parte es la consulta {} partido, el filtrado de los documentos a la actualización (en nuestro caso todos los documentos).

  • La segunda parte [{ $set: { name: { ... } }] es la tubería actualización agregación (tenga en cuenta los corchetes significan el uso de una tubería de agregación). $set es un nuevo operador de agregación y un alias de $addFields.

  • No se olvide { multi: true }, de lo contrario sólo el primer documento correspondiente se actualizará.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top