Comment mettre à jour le _id d'un document MongoDB?

129

Je veux mettre à jour un _idchamp d'un document. Je sais que ce n'est pas une très bonne pratique. Mais pour une raison technique, j'ai besoin de le mettre à jour. Si j'essaye de le mettre à jour, j'obtiens:

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

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

Et la mise à jour n'est pas faite. Comment puis-je le mettre à jour?

Shingara
la source

Réponses:

216

Vous ne pouvez pas le mettre à jour. Vous devrez enregistrer le document en utilisant un nouveau _id, puis supprimer l'ancien document.

// 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
la source
29
Un problème amusant avec cela apparaît si un champ de ce document a un index unique. Dans ce cas, votre exemple échouera car un document ne peut pas être inséré avec une valeur en double dans un champ indexé unique. Vous pouvez résoudre ce problème en effectuant d'abord la suppression, mais c'est une mauvaise idée car si votre insertion échoue pour une raison quelconque, vos données sont maintenant perdues. Vous devez à la place supprimer votre index, effectuer le travail, puis restaurer l'index.
skelly
Bon point @skelly! J'ai pensé à des problèmes similaires et j'ai vu votre nouveau commentaire fait il y a à peine 2 heures. Donc, ce tracas de modification de l'ID est considéré comme un problème intrinsèque causé par le fait de permettre à l'utilisateur de choisir l'ID?
RayLuo
1
Si vous obtenez un duplicate key errorsur la insertligne et que vous n'êtes pas inquiet du problème mentionné par @skelly, la solution la plus simple consiste simplement à appeler la removeligne en premier, puis à appeler la insertligne. Le docdevrait déjà être imprimé sur votre écran afin qu'il soit facile à récupérer, dans le pire des cas, même si l'insertion échoue, pour les documents simples.
philfreo
Utiliser juste ObjectId () sans la chaîne comme paramètre générera un nouveau paramètre unique.
Erik
1
@ShankhadeepGhoshal ouais, c'est un risque, surtout si vous effectuez cela contre un système de production en direct. Malheureusement, je pense que votre meilleure option est de prendre une interruption programmée et d'arrêter tous les écrivains pendant ce processus. Une autre option qui pourrait être un peu moins pénible serait de forcer temporairement les applications en mode lecture seule. Reconfigurez toutes les applications qui écrivent dans la base de données pour qu'elles pointent uniquement vers un nœud secondaire. Les lectures réussiront mais les écritures échoueront pendant ce temps et votre base de données restera statique.
skelly
32

Pour le faire pour toute votre collection, vous pouvez également utiliser une boucle (basée sur l'exemple Niels):

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

Dans ce cas, UserId était le nouvel ID que je voulais utiliser

Patrick Wolf
la source
1
Un snapshot () sur find serait-il conseillé d'empêcher le forEach de récupérer accidentellement des documents plus récents lors de son itération?
John Flinchbaugh
Cet extrait de code ne se termine jamais. Il continue à itérer encore et encore dans la collection pour toujours. L'instantané ne fait pas ce que vous attendez (vous pouvez le tester en prenant un `` instantané '' en ajoutant un document à la collection, puis en voyant que ce nouveau document est dans l'instantané)
Patrick
Voir stackoverflow.com/a/28083980/305324 pour une alternative au snapshot. list()est la logique, mais pour les grandes bases de données, cela peut épuiser la mémoire
Patrick
4
Euh, cela a 11 votes positifs mais quelqu'un dit que c'est une boucle infinie? Quel est le problème ici?
Andrew
4
@Andrew parce que la culture contemporaine des codeurs pop vous oblige à toujours reconnaître une bonne entrée avant de vérifier que ladite entrée fonctionne réellement.
csvan
5

Dans le cas où vous souhaitez renommer _id dans la même collection (par exemple, si vous souhaitez préfixer certains _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) {... nécessaire pour empêcher une boucle infinie, puisque forEach choisit les documents insérés, même via la méthode .snapshot () utilisée.

marque
la source
find(...).snapshot is not a functionmais à part ça, excellente solution. De plus, si vous souhaitez le remplacer _idpar votre identifiant personnalisé, vous pouvez vérifier si vous doc._id.toString().length === 24souhaitez empêcher la boucle infinie (en supposant que vos identifiants personnalisés ne comportent pas également 24 caractères ),
Dan Dascalescu
1

Ici, j'ai une solution qui évite les demandes multiples, pour les boucles et la suppression d'anciens documents.

Vous pouvez facilement créer une nouvelle idée manuellement en utilisant quelque chose comme: _id:ObjectId() Mais sachant que Mongo attribuera automatiquement un _id s'il est manquant, vous pouvez utiliser un agrégat pour créer un $projectcontenant tous les champs de votre document, mais omettez le champ _id. Vous pouvez ensuite l'enregistrer avec$out

Donc, si votre document est:

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

Ensuite, votre requête sera:

    db.getCollection('myCollection').aggregate([
        {$match:
             {_id: ObjectId("5b5ed345cfbce6787588e480")}
        }        
        {$project:
            {
             title: '$title',
             description: '$description'             
            }     
        },
        {$out: 'myCollection'}
    ])
Florent Arlandis
la source
Idée intéressante ... mais vous voulez généralement définir _idune valeur donnée, plutôt que de laisser MongoDB en générer une autre.
Dan Dascalescu le
0

Vous pouvez également créer un nouveau document à partir de la boussole MongoDB ou en utilisant la commande et définir la specific _idvaleur souhaitée.

Talha Noyon
la source