Como atualizar o _id de um documento do MongoDB?

129

Quero atualizar um _idcampo de um documento. Eu sei que não é uma boa prática. Mas, por algum motivo técnico, preciso atualizá-lo. Se eu tentar atualizá-lo, recebo:

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

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

E a atualização não é feita. Como posso atualizá-lo?

shingara
fonte

Respostas:

216

Você não pode atualizá-lo. Você precisará salvar o documento usando um novo _ide remover o documento antigo.

// 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
fonte
29
Um problema divertido com isso aparece se algum campo desse documento tiver um índice exclusivo. Nessa situação, seu exemplo falhará porque um documento não pode ser inserido com um valor duplicado em um campo indexado exclusivo. Você pode corrigir isso fazendo a exclusão primeiro, mas isso é uma péssima idéia, porque se sua inserção falhar por algum motivo, seus dados serão perdidos. Em vez disso, você deve soltar seu índice, executar o trabalho e, em seguida, restaurar o índice.
skelly
Bom ponto @skelly! Por acaso pensei em problemas semelhantes e vi seu novo comentário feito apenas duas horas atrás. Portanto, esse problema de identificação de modificação é considerado um problema intrínseco causado pela permissão do usuário para escolher a identificação?
precisa saber é o seguinte
1
Se você entrar duplicate key errorna insertfila e não estiver preocupado com o problema mencionado por @elly, a solução mais simples é ligar removeprimeiro para a linha e depois para a insertlinha. O docarquivo já deve estar impresso na tela para facilitar a recuperação, na pior das hipóteses, mesmo se a inserção falhar, para documentos simples.
philfreo
Usar apenas ObjectId () sem a string como parâmetro gerará uma nova e exclusiva.
30615 Erik
1
@ ShankhadeepGhoshal Sim, isso é um risco, principalmente se você estiver realizando isso em um sistema de produção ao vivo. Infelizmente, acho que sua melhor opção é fazer uma interrupção programada e interromper todos os escritores durante esse processo. Outra opção que pode ser um pouco menos dolorosa seria forçar aplicativos no modo somente leitura temporariamente. Reconfigure todos os aplicativos que gravam no banco de dados para apontar apenas para um nó secundário. As leituras serão bem-sucedidas, mas as gravações falharão durante esse período e seu banco de dados permanecerá estático.
Skelly
32

Para fazer isso para toda a sua coleção, você também pode usar um loop (baseado no exemplo de Niels):

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

Nesse caso, UserId era o novo ID que eu queria usar

Patrick Wolf
fonte
1
Um snapshot () na localização seria aconselhado a evitar que o forEach recolhas acidentalmente documentos mais recentes à medida que itera?
John Flinchbaugh 14/05
Este trecho de código nunca é concluído. Ele continua repetindo a coleção para sempre. Snapshot não faz o que você espera (você pode testá-lo por tomar um 'instantâneo' a adição de um documento para a coleção, em seguida, vendo que esse novo documento é no instantâneo)
Patrick
Consulte stackoverflow.com/a/28083980/305324 para obter uma alternativa ao instantâneo. list()é a lógica, mas para grandes bases de dados que pode esgotar a memória
Patrick
4
Isso tem 11 votos positivos, mas alguém diz que é um loop infinito? Qual é o problema aqui?
Andrew
4
@ Andrew, porque a cultura contemporânea de codificadores pop exige que você sempre reconheça boas informações antes de realmente verificar se essas informações realmente funcionam.
csvan
5

No caso, você deseja renomear _id na mesma coleção (por exemplo, se quiser prefixar alguns _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) {... necessário para evitar loop infinito, pois forEach seleciona os documentos inseridos, mesmo através do método .snapshot () usado.

Marca
fonte
find(...).snapshot is not a functionmas fora isso, ótima solução. Além disso, se você deseja substituir _idcom o seu ID de costume, você pode verificar se doc._id.toString().length === 24a evitar que o loop infinito (assumindo que seus IDs personalizados não são também 24 caracteres ),
Dan Dascalescu
1

Aqui eu tenho uma solução que evita várias solicitações, para loops e remoção de documentos antigos.

Você pode criar facilmente uma nova ideia manualmente usando algo como: _id:ObjectId() Mas sabendo que o Mongo atribuirá automaticamente um _id se estiver ausente, você pode usar o agregado para criar um $projectcontendo todos os campos do seu documento, mas omita o campo _id. Você pode salvá-lo com$out

Portanto, se o seu documento for:

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

Então sua consulta será:

    db.getCollection('myCollection').aggregate([
        {$match:
             {_id: ObjectId("5b5ed345cfbce6787588e480")}
        }        
        {$project:
            {
             title: '$title',
             description: '$description'             
            }     
        },
        {$out: 'myCollection'}
    ])
Florent Arlandis
fonte
Ideia interessante ... mas geralmente você deseja definir o _idvalor especificado, em vez de permitir que o MongoDB gere outro.
Dan Dascalescu
0

Você também pode criar um novo documento a partir da bússola do MongoDB ou usando o comando e definir o specific _idvalor que deseja.

Talha Noyon
fonte