Come aggiornare il _id di un documento MongoDB?


129

Voglio aggiornare un _idcampo di un documento. So che non è davvero una buona pratica. Ma con qualche motivo tecnico, devo aggiornarlo. Se provo ad aggiornarlo ottengo:

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

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

E l'aggiornamento non è stato effettuato. Come posso aggiornarlo?

Risposte:


216

Non è possibile aggiornarlo. Dovrai salvare il documento usando un nuovo _id, quindi rimuovere il vecchio documento.

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

29
Un problema divertente con questo appare se un campo in quel documento ha un indice univoco. In tale situazione, l'esempio fallirà perché non è possibile inserire un documento con un valore duplicato in un campo indicizzato univoco. È possibile risolvere questo problema eseguendo prima l'eliminazione, ma è una cattiva idea perché se l'inserimento non riesce per qualche motivo i dati ora vengono persi. È invece necessario eliminare l'indice, eseguire il lavoro, quindi ripristinare l'indice.
skelly,

Buon punto @skelly! Mi è capitato di pensare a problemi simili e ho visto il tuo nuovo commento fatto solo 2 ore fa. Quindi questa seccatura id di modifica è considerata come un problema intrinseco causato dal consentire all'utente di scegliere l'ID?
RayLuo,

1
Se ottieni un duplicate key errorin insertlinea e non sei preoccupato per il problema menzionato da @skelly, la soluzione più semplice è chiamare removeprima la linea e poi chiamare la insertlinea. L' docdovrebbe già stampata sul vostro schermo in modo che sarebbe stato facile recuperare, caso peggiore, anche se l'inserimento non riesce, per i documenti semplici.
philfreo,

L'uso di solo ObjectId () senza la stringa come parametro genererà un nuovo unico.
Erik,

1
@ShankhadeepGhoshal sì, questo è un rischio, in particolare se lo stai eseguendo contro un sistema di produzione live. Sfortunatamente penso che la tua migliore opzione sia quella di prendere un'interruzione programmata e fermare tutti gli scrittori durante questo processo. Un'altra opzione che potrebbe essere un po 'meno dolorosa sarebbe quella di forzare temporaneamente le applicazioni in una modalità di sola lettura. Riconfigurare tutte le app che scrivono nel DB in modo che puntino solo a un nodo secondario. Le letture avranno esito positivo ma le scritture falliranno durante questo periodo e il DB rimarrà statico.
skelly il

32

Per farlo per l'intera collezione puoi anche usare un loop (basato sull'esempio di Niels):

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

In questo caso UserId era il nuovo ID che volevo usare


1
Si consiglia a uno snapshot () di trovare di impedire a forEach di raccogliere accidentalmente nuovi documenti mentre scorre?
John Flinchbaugh,

Questo frammento di codice non viene mai completato. Continua a ripetere per sempre la collezione. Lo snapshot non fa ciò che ti aspetti (puoi testarlo facendo uno 'snapshot' aggiungendo un documento alla raccolta, quindi vedendo che quel nuovo documento è nell'istantanea)
Patrick

Vedere stackoverflow.com/a/28083980/305324 per un'alternativa all'istantanea. list()è quello logico, ma per grandi database questo può esaurire la memoria
Patrick,

4
Uh, questo ha 11 voti positivi, ma qualcuno dice che è un ciclo infinito? Qual è il problema qui?
Andrew,

4
@Andrew perché la cultura del coder pop contemporaneo impone che si debba sempre riconoscere un buon input prima di verificare che tale input funzioni effettivamente.
CSV

5

Nel caso, desideri rinominare _id nella stessa raccolta (ad esempio, se vuoi aggiungere un prefisso ad alcuni _id):

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) {... necessario per impedire il ciclo infinito, poiché forEach seleziona i documenti inseriti, anche attraverso il metodo .snapshot () utilizzato.


find(...).snapshot is not a functionma a parte questo, ottima soluzione. Inoltre, se desideri sostituire il _idtuo ID personalizzato, puoi verificare se doc._id.toString().length === 24impedire il ciclo infinito (supponendo che anche i tuoi ID personalizzati non siano lunghi 24 caratteri ),
Dan Dascalescu,

1

Qui ho una soluzione che evita molteplici richieste, per loop e rimozione di vecchi documenti.

Puoi facilmente creare una nuova idea manualmente usando qualcosa come: _id:ObjectId() Ma sapendo che Mongo assegnerà automaticamente un _id se mancante, puoi usare aggregate per creare un $projectcontenente tutti i campi del tuo documento, ma ometti il ​​campo _id. È quindi possibile salvarlo con$out

Quindi se il tuo documento è:

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

Quindi la tua query sarà:

    db.getCollection('myCollection').aggregate([
        {$match:
             {_id: ObjectId("5b5ed345cfbce6787588e480")}
        }        
        {$project:
            {
             title: '$title',
             description: '$description'             
            }     
        },
        {$out: 'myCollection'}
    ])

Idea interessante ... ma di solito si desidera impostare il _idvalore su un determinato valore, piuttosto che lasciare che MongoDB ne generi un altro.
Dan Dascalescu il

0

Puoi anche creare un nuovo documento dalla bussola MongoDB o usando il comando e impostare il specific _idvalore desiderato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.