Як оновити _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

Гарна точка @skelly! Я випадково задумався про подібні проблеми і побачив ваш свіжий коментар, зроблений лише дві години тому. Тож ця проблема з модифікацією id розглядається як внутрішня проблема, викликана тим, що дозволяє користувачеві вибирати ідентифікатор?
RayLuo

1
Якщо ви потрапляєте duplicate key errorв insertрядок і не переживаєте з приводу проблеми @skelly, найпростішим рішенням є просто removeспочатку зателефонувати на лінію, а потім зателефонувати на insertлінію. Це docвже надруковано на вашому екрані, щоб його було легко відновити, в гіршому випадку, навіть якщо вставка не вдалася, для простих документів.
philfreo

Використання просто ObjectId () без рядка в якості параметра генерує новий унікальний.
Ерік

1
@ShankhadeepGhoshal Так, це ризик, особливо якщо ви це виконуєте проти живої системи виробництва. На жаль, я думаю, що найкращим варіантом є зняття планового відключення та зупинення всіх авторів під час цього процесу. Ще одним варіантом, який може бути трохи менш болісним, буде тимчасово змушувати програми переходити в режим лише для читання. Переконфігуруйте всі додатки, які записують в DB, ​​щоб він вказував лише на вторинний вузол. Читання буде успішним, але записи не вдасться протягом цього часу, і ваша БД залишиться статичною.
skelly

32

Щоб зробити це для всієї колекції, ви також можете скористатися циклом (на прикладі Niels):

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

У цьому випадку UserId був новим ідентифікатором, який я хотів використати


1
Чи порадив би знімок () у знаходженні не рекомендувати для випадкового підбору нових документів, коли він повторюється, 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() Але знаючи, що Монго автоматично призначить _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.