¿Cómo actualizar el _id de un documento MongoDB?

129

Quiero actualizar un _idcampo de un documento. Sé que no es una muy buena práctica. Pero con alguna razón técnica, necesito actualizarlo. Si trato de actualizarlo obtengo:

> db.clients.update({ _id: ObjectId("123")}, { $set: { _id: ObjectId("456")}})

Performing an update on the path '_id' would modify the immutable field '_id'

Y la actualización no se realiza. ¿Cómo puedo actualizarlo?

shingara
fuente

Respuestas:

216

No puedes actualizarlo. Deberá guardar el documento con uno nuevo _idy luego eliminar el documento anterior.

// store the document in a variable
doc = db.clients.findOne({_id: ObjectId("4cc45467c55f4d2d2a000002")})

// set a new _id on the document
doc._id = ObjectId("4c8a331bda76c559ef000004")

// insert the document, using the new _id
db.clients.insert(doc)

// remove the document with the old _id
db.clients.remove({_id: ObjectId("4cc45467c55f4d2d2a000002")})
Niels van der Rest
fuente
29
Un problema divertido con esto aparece si algún campo en ese documento tiene un índice único. En esa situación, su ejemplo fallará porque no se puede insertar un documento con un valor duplicado en un campo indexado único. Puede solucionar esto haciendo la eliminación primero, pero esa es una mala idea porque si su inserción falla por alguna razón, sus datos ahora se pierden. En su lugar, debe soltar su índice, realizar el trabajo y luego restaurar el índice.
skelly
Buen punto @skelly! Me pasó a pensar en problemas similares y vi tu nuevo comentario hace solo 2 horas. Entonces, ¿este problema de modificación de id se considera como un problema intrínseco causado al permitir que el usuario elija ID?
RayLuo
1
Si se obtiene una duplicate key erroren la insertlínea y no están preocupados por el problema @skelly mencionado, la solución más sencilla es simplemente llamar a la removelínea primero y luego llamar a la insertlínea. El docya debería estar impreso en su pantalla para que sea fácil de recuperar, en el peor de los casos, incluso si falla la inserción, para documentos simples.
philfreo
Usar solo ObjectId () sin la cadena como parámetro generará uno nuevo y único.
Erik
1
@ShankhadeepGhoshal, sí, eso es un riesgo, especialmente si está realizando esto contra un sistema de producción en vivo. Desafortunadamente, creo que su mejor opción es tomar una interrupción programada y detener a todos los escritores durante este proceso. Otra opción que podría ser un poco menos dolorosa sería forzar las aplicaciones a un modo de solo lectura temporalmente. Vuelva a configurar todas las aplicaciones que escriben en la base de datos para que solo apunten a un nodo secundario. Las lecturas serán exitosas pero las escrituras fallarán durante este tiempo, y su DB permanecerá estática.
skelly
32

Para hacerlo para toda su colección, también puede usar un bucle (basado en el ejemplo de Niels):

db.status.find().forEach(function(doc){ 
    doc._id=doc.UserId; db.status_new.insert(doc);
});
db.status_new.renameCollection("status", true);

En este caso, UserId era la nueva ID que quería usar

Patrick Wolf
fuente
1
¿Se recomendaría una instantánea () en find para evitar que forEach recoja accidentalmente documentos más nuevos a medida que itera?
John Flinchbaugh
Este fragmento de código nunca se completa. Sigue iterando una y otra vez la colección para siempre. La instantánea no hace lo que espera (puede probarla tomando una 'instantánea' agregando un documento a la colección, y luego viendo que ese nuevo documento está en la instantánea)
Patrick
Consulte stackoverflow.com/a/28083980/305324 para obtener una alternativa a la instantánea. list()es el lógico, pero para grandes bases de datos esto puede agotar la memoria
Patrick
44
¿Tiene 11 votos a favor pero alguien dice que es un ciclo infinito? ¿Cuál es el trato aquí?
Andrew
44
@Andrew porque la cultura contemporánea del codificador pop dicta que siempre debes reconocer los buenos comentarios antes de verificar que dichos comentarios realmente funcionen.
csvan
5

En el caso, desea cambiar el nombre de _id en la misma colección (por ejemplo, si desea prefijar algunos _ids):

db.someCollection.find().snapshot().forEach(function(doc) { 
   if (doc._id.indexOf("2019:") != 0) {
       print("Processing: " + doc._id);
       var oldDocId = doc._id;
       doc._id = "2019:" + doc._id; 
       db.someCollection.insert(doc);
       db.someCollection.remove({_id: oldDocId});
   }
});

if (doc._id.indexOf ("2019:")! = 0) {... necesario para evitar el bucle infinito, ya que forEach selecciona los documentos insertados, incluso a través del método .snapshot () utilizado.

marca
fuente
find(...).snapshot is not a functionpero aparte de eso, gran solución. Además, si desea reemplazar _idcon su identificación personalizada, puede verificar si se doc._id.toString().length === 24evita el bucle infinito (suponiendo que sus identificaciones personalizadas no tengan también 24 caracteres de largo ),
Dan Dascalescu
1

Aquí tengo una solución que evita múltiples solicitudes, para bucles y eliminación de documentos antiguos.

Puede crear fácilmente una nueva idea manualmente usando algo como: _id:ObjectId() Pero sabiendo que Mongo asignará automáticamente un _id si falta, puede usar el agregado para crear un que $projectcontenga todos los campos de su documento, pero omita el campo _id. Luego puede guardarlo con$out

Entonces, si su documento es:

{
"_id":ObjectId("5b5ed345cfbce6787588e480"),
"title": "foo",
"description": "bar"
}

Entonces su consulta será:

    db.getCollection('myCollection').aggregate([
        {$match:
             {_id: ObjectId("5b5ed345cfbce6787588e480")}
        }        
        {$project:
            {
             title: '$title',
             description: '$description'             
            }     
        },
        {$out: 'myCollection'}
    ])
Florent Arlandis
fuente
Idea interesante ... pero generalmente desea establecer _idun valor dado, en lugar de dejar que MongoDB genere otro.
Dan Dascalescu
0

También puede crear un nuevo documento desde la brújula MongoDB o mediante el comando y establecer el specific _idvalor que desee.

Talha Noyon
fuente