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ć?

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

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ć


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 () .


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'}
    ])

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ść.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.