Jak zaktualizować _id jednego dokumentu MongoDB?

135

Chcę zaktualizować _idpole jednego dokumentu. Wiem, że to nie jest dobra praktyka. Ale z jakiegoś powodu technicznego potrzebuję go zaktualizować. Jeśli spróbuję go zaktualizować, otrzymuję:

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

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

A aktualizacja nie jest wykonywana. Jak mogę to zaktualizować?

shingara
źródło

Odpowiedzi:

224

Nie możesz go zaktualizować. Będziesz musiał zapisać dokument, używając nowego _id, a następnie usunąć stary dokument.

// 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
źródło
29
Zabawny problem z tym pojawia się, jeśli jakieś pole w tym dokumencie ma unikalny indeks. W takiej sytuacji przykład zakończy się niepowodzeniem, ponieważ nie można wstawić dokumentu ze zduplikowaną wartością w unikatowym indeksowanym polu. Możesz to naprawić, wykonując najpierw usuwanie, ale jest to zły pomysł, ponieważ jeśli wstawianie nie powiedzie się z jakiegoś powodu, Twoje dane zostaną utracone. Zamiast tego należy usunąć indeks, wykonać pracę, a następnie przywrócić indeks.
skelly
Słuszna uwaga @skelly! Zdarzyło mi się myśleć o podobnych problemach i zobaczyłem Twój nowy komentarz, który został napisany zaledwie 2 godziny temu. Więc ten problem z modyfikacją identyfikatora jest uważany za nieodłączny problem spowodowany zezwoleniem użytkownikowi na wybór identyfikatora?
RayLuo
1
Jeśli pojawi się znak A duplicate key errorna insertlinii i nie martwisz się problemem, o którym wspomniał @skelly, najprostszym rozwiązaniem jest po prostu zadzwonić removenajpierw na linię, a następnie na nią insert. docPowinien już wydrukowane na ekranie tak, że to będzie łatwe do odzyskania, w najgorszym przypadku, nawet jeśli wkładka nie powiedzie, dla prostych dokumentów.
philfreo
Użycie po prostu ObjectId () bez łańcucha jako parametru wygeneruje nowy, unikalny.
Erik
1
@ShankhadeepGhoshal tak, to jest ryzyko, szczególnie jeśli wykonujesz to w systemie produkcyjnym na żywo. Niestety myślę, że najlepszą opcją jest zaplanowana przerwa w działaniu i zatrzymanie wszystkich pisarzy podczas tego procesu. Inną opcją, która może być nieco mniej bolesna, byłoby tymczasowe wymuszenie przejścia aplikacji w tryb tylko do odczytu. Skonfiguruj ponownie wszystkie aplikacje, które zapisują dane do bazy danych, tak aby wskazywały tylko na węzeł dodatkowy. Odczyt powiedzie się, ale zapis nie powiedzie się w tym czasie, a baza danych pozostanie statyczna.
skelly
32

Aby to zrobić dla całej swojej kolekcji, możesz również użyć pętli (na przykładzie Nielsa):

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

W tym przypadku UserId był nowym identyfikatorem, którego chciałem użyć

Patrick Wolf
źródło
1
Czy zaleca się, aby funkcja snapshot () na znalezieniu nie powodowała przypadkowego pobierania przez forEach nowszych dokumentów podczas iteracji?
John Flinchbaugh
Ten fragment kodu nigdy się nie kończy. Ciągle iteruje kolekcję na zawsze. Migawka nie robi tego, czego się spodziewasz (możesz ją przetestować, wykonując `` migawkę '', dodając dokument do kolekcji, a następnie widząc, że ten nowy dokument jest na migawce)
Patrick
Zobacz stackoverflow.com/a/28083980/305324, aby uzyskać alternatywę dla migawki. list()jest logiczne, ale w przypadku dużych baz danych może to wyczerpać pamięć
Patrick
4
Uh, to ma 11 głosów za, ale ktoś mówi, że to nieskończona pętla? O co tu chodzi?
Andrew,
4
@Andrew, ponieważ współczesna kultura pop-koderów mówi, że zawsze powinieneś potwierdzić dobry wkład, zanim faktycznie sprawdzisz, czy rzeczywiście działa.
csvan
5

W przypadku, gdy chcesz zmienić nazwę _id w tej samej kolekcji (na przykład, jeśli chcesz poprzedzić niektóre _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) {... potrzebne, aby zapobiec nieskończonej pętli, ponieważ forEach wybiera wstawione dokumenty, nawet przy użyciu metody .snapshot () .

znak
źródło
find(...).snapshot is not a functionale poza tym świetne rozwiązanie. Ponadto, jeśli chcesz zastąpić _idswój niestandardowy identyfikator, możesz sprawdzić, czy doc._id.toString().length === 24zapobiec nieskończonej pętli (zakładając, że twoje niestandardowe identyfikatory nie mają również 24 znaków ),
Dan Dascalescu
1

Tutaj mam rozwiązanie, które pozwala uniknąć wielu żądań, pętli i usuwania starych dokumentów.

Możesz łatwo utworzyć nowy pomysł ręcznie, używając czegoś takiego jak: _id:ObjectId() Ale wiedząc, że Mongo automatycznie przypisze _id, jeśli go brakuje, możesz użyć agregacji, aby utworzyć $projectzawierającą wszystkie pola dokumentu, ale pominąć pole _id. Następnie możesz go zapisać za pomocą$out

Więc jeśli twój dokument to:

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

Wtedy twoje zapytanie będzie:

    db.getCollection('myCollection').aggregate([
        {$match:
             {_id: ObjectId("5b5ed345cfbce6787588e480")}
        }        
        {$project:
            {
             title: '$title',
             description: '$description'             
            }     
        },
        {$out: 'myCollection'}
    ])
Florent Arlandis
źródło
Ciekawy pomysł ... ale zwykle chcesz ustawić na _iddaną wartość, zamiast pozwolić MongoDB wygenerować inną.
Dan Dascalescu
0

Możesz także utworzyć nowy dokument z kompasu MongoDB lub za pomocą polecenia i ustawić żądaną specific _idwartość.

Talha Noyon
źródło