Как обновить _id одного документа MongoDB?


129

Я хочу обновить _idполе одного документа. Я знаю, что это не очень хорошая практика. Но по какой-то технической причине мне нужно его обновить. Если я попытаюсь обновить его, я получу:

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

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

И обновление не производится. Как мне его обновить?

Ответы:


216

Вы не можете его обновить. Вам нужно будет сохранить документ в новом _id, а затем удалить старый документ.

// 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
Интересная проблема возникает, если какое-то поле в этом документе имеет уникальный индекс. В этой ситуации ваш пример потерпит неудачу, потому что документ не может быть вставлен с повторяющимся значением в уникальное индексированное поле. Вы можете исправить это, выполнив сначала удаление, но это плохая идея, потому что, если ваша вставка по какой-то причине не работает, ваши данные теперь теряются. Вместо этого вы должны удалить свой индекс, выполнить работу, а затем восстановить индекс.
skelly 02

Хороший момент @skelly! Я подумал о подобных проблемах и увидел ваш свежий комментарий, сделанный всего 2 часа назад. Таким образом, эта проблема с изменением идентификатора рассматривается как внутренняя проблема, вызванная тем, что пользователь может выбрать идентификатор?
RayLuo 02

1
Если вы получили duplicate key errorв insertстроке и не беспокоитесь о проблеме, упомянутой @skelly, самое простое решение - removeсначала позвонить на линию, а затем позвонить на нее insert. Он docуже должен быть напечатан на вашем экране, чтобы его можно было легко восстановить, в худшем случае, даже если вставка не удалась, для простых документов.
philfreo

Использование только ObjectId () без строки в качестве параметра создаст новый уникальный.
Эрик

1
@ShankhadeepGhoshal: да, это риск, особенно если вы выполняете это против живой производственной системы. К сожалению, я думаю, что ваш лучший вариант - запланировать отключение и остановить всех писателей во время этого процесса. Другой вариант, который может быть немного менее болезненным, - это временно принудительно перевести приложения в режим только для чтения. Перенастройте все приложения, которые пишут в БД, так, чтобы они указывали только на вторичный узел. Чтение будет успешным, но запись в это время завершится ошибкой, и ваша БД останется статичной.
skelly

32

Чтобы сделать это для всей вашей коллекции, вы также можете использовать цикл (на основе примера Нильса):

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

В этом случае UserId был новым идентификатором, который я хотел использовать.


1
Можно ли было бы посоветовать snapshot () в find, чтобы forEach не мог случайно получить новые документы во время итерации?
Джон Флинчбо

Этот фрагмент кода никогда не завершается. Он постоянно повторяет коллекцию снова и снова. Снимок не делает того, что вы ожидаете (вы можете проверить его, сделав «снимок», добавив документ в коллекцию, а затем увидев, что этот новый документ находится в снимке)
Патрик

См. Stackoverflow.com/a/28083980/305324 для альтернативы моментальному снимку. list()логично, но для больших баз данных это может истощить память
Патрик

4
У этого есть 11 голосов, но кто-то говорит, что это бесконечный цикл? В чем дело?
Эндрю

4
@Andrew, потому что современная культура поп-кодеров диктует, что вы всегда должны подтверждать хороший ввод, прежде чем фактически проверять, действительно ли он работает.
csvan

5

В случае, если вы хотите переименовать _id в той же коллекции (например, если вы хотите добавить префикс к некоторым _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) {... необходимо для предотвращения бесконечного цикла, поскольку forEach выбирает вставленные документы, даже с помощью используемого метода .snapshot () .


find(...).snapshot is not a functionно кроме этого, отличное решение. Кроме того, если вы хотите заменить _idсвой собственный идентификатор, вы можете проверить, следует ли doc._id.toString().length === 24предотвратить бесконечный цикл (при условии, что ваши собственные идентификаторы также не содержат 24 символа ),
Дэн Даскалеску

1

Здесь у меня есть решение, позволяющее избежать многократных запросов, циклов и удаления старых документов.

Вы можете легко создать новую идею вручную, используя что-то вроде: _id:ObjectId() Но зная, что Mongo автоматически назначит _id, если он отсутствует, вы можете использовать агрегат для создания, $projectсодержащего все поля вашего документа, но опустите поле _id. Затем вы можете сохранить его с помощью$out

Итак, если ваш документ:

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

Тогда ваш запрос будет:

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

Интересная идея ... но обычно вы хотите установить _idдля параметра заданное значение, а не позволять MongoDB сгенерировать другое.
Дэн Даскалеску

0

Вы также можете создать новый документ из компаса MongoDB или с помощью команды и установить нужное specific _idзначение.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.