Làm cách nào để cập nhật _id của một Tài liệu MongoDB?


129

Tôi muốn cập nhật một _idlĩnh vực của một tài liệu. Tôi biết nó không phải là một thực sự tốt. Nhưng với một số lý do kỹ thuật, tôi cần cập nhật nó. Nếu tôi cố cập nhật nó, tôi nhận được:

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

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

Và bản cập nhật không được thực hiện. Làm thế nào tôi có thể cập nhật nó?

Câu trả lời:


216

Bạn không thể cập nhật nó. Bạn sẽ phải lưu tài liệu bằng cách sử dụng một tài liệu mới _idvà sau đó xóa tài liệu cũ.

// 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
Một vấn đề thú vị với điều này xuất hiện nếu một số trường trên tài liệu đó có một chỉ mục duy nhất. Trong tình huống đó, ví dụ của bạn sẽ thất bại vì tài liệu không thể được chèn với giá trị trùng lặp trong trường được lập chỉ mục duy nhất. Bạn có thể khắc phục điều này bằng cách xóa đầu tiên, nhưng đó là một ý tưởng tồi bởi vì nếu chèn của bạn không thành công vì một số lý do, dữ liệu của bạn bây giờ bị mất. Thay vào đó, bạn phải bỏ chỉ mục của mình, thực hiện công việc, sau đó khôi phục chỉ mục.
skelly

Điểm tốt @skelly! Tôi tình cờ nghĩ về những vấn đề tương tự và thấy bình luận mới của bạn được đưa ra chỉ 2 giờ trước. Vì vậy, rắc rối id sửa đổi này được coi là một vấn đề nội tại gây ra bằng cách cho phép người dùng chọn ID?
RayLuo

1
Nếu bạn nhận được một duplicate key errortrong các insertdòng và không lo lắng về vấn đề này @skelly đề cập, giải pháp đơn giản nhất là chỉ cần gọi các removedòng đầu tiên và sau đó gọi các insertdòng. Các docnên đã in trên màn hình của bạn để nó muốn được dễ dàng để phục hồi, trường hợp xấu nhất, ngay cả khi chèn thất bại, cho các tài liệu đơn giản.
philfreo

Chỉ sử dụng ObjectId () không có chuỗi làm tham số sẽ tạo ra một tham số duy nhất mới.
Erik

1
@ShankhadeepGhoshal yeah đó là một rủi ro, đặc biệt nếu bạn đang thực hiện điều này chống lại một hệ thống sản xuất trực tiếp. Thật không may, tôi nghĩ rằng lựa chọn tốt nhất của bạn là mất điện theo lịch trình và dừng tất cả các nhà văn trong quá trình này. Một tùy chọn khác có thể ít đau đớn hơn là tạm thời buộc các ứng dụng vào chế độ chỉ đọc. Cấu hình lại tất cả các ứng dụng ghi tot anh DB để chỉ trỏ đến một nút phụ. Đọc sẽ thành công nhưng viết sẽ thất bại trong thời gian này và DB của bạn sẽ ở trạng thái tĩnh.
skelly

32

Để làm điều đó cho toàn bộ bộ sưu tập của bạn, bạn cũng có thể sử dụng một vòng lặp (dựa trên ví dụ Niels):

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

Trong trường hợp này, UserId là ID mới tôi muốn sử dụng


1
Một snapshot () khi tìm được khuyên nên giữ forEach không vô tình nhặt các tài liệu mới hơn khi nó lặp lại?
John Flinchbaugh

Đoạn mã này không bao giờ hoàn thành. Nó cứ lặp đi lặp lại mãi mãi trong bộ sưu tập. Ảnh chụp nhanh không làm những gì bạn mong đợi (bạn có thể kiểm tra nó bằng cách chụp 'ảnh chụp nhanh' thêm tài liệu vào bộ sưu tập, sau đó thấy tài liệu mới đó có trong ảnh chụp nhanh)
Patrick

Xem stackoverflow.com/a/28083980/305324 để biết cách thay thế cho ảnh chụp nhanh. list()là một logic, nhưng đối với các cơ sở dữ liệu lớn, điều này có thể làm cạn kiệt bộ nhớ
Patrick

4
Uh, cái này có 11 upvote nhưng ai đó nói nó là một vòng lặp vô hạn? Thỏa thuận ở đây là gì?
Andrew

4
@Andrew vì văn hóa pop-coder đương đại ra lệnh rằng bạn phải luôn thừa nhận đầu vào tốt trước khi thực sự xác minh rằng đầu vào thực sự hoạt động.
csvan

5

Trong trường hợp, bạn muốn đổi tên _id trong cùng một bộ sưu tập (ví dụ: nếu bạn muốn tiền tố một số _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) {... cần thiết để ngăn chặn vòng lặp vô hạn, vì forEach chọn các tài liệu được chèn, thậm chí phương thức .snapshot () được sử dụng.


find(...).snapshot is not a functionnhưng khác hơn, giải pháp tuyệt vời. Ngoài ra, nếu bạn muốn thay thế _idbằng id tùy chỉnh của mình, bạn có thể kiểm tra xem có doc._id.toString().length === 24ngăn vòng lặp vô hạn không (giả sử ID tùy chỉnh của bạn không dài 24 ký tự ),
Dan Dascalescu

1

Ở đây tôi có một giải pháp tránh nhiều yêu cầu, cho các vòng lặp và loại bỏ tài liệu cũ.

Bạn có thể dễ dàng tạo một ý tưởng mới theo cách thủ công bằng cách sử dụng một cái gì đó như: _id:ObjectId() Nhưng biết Mongo sẽ tự động gán một _id nếu bị thiếu, bạn có thể sử dụng tổng hợp để tạo một $projectchứa tất cả các trường của tài liệu của bạn, nhưng bỏ qua trường _id. Sau đó bạn có thể lưu nó với$out

Vì vậy, nếu tài liệu của bạn là:

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

Sau đó, truy vấn của bạn sẽ là:

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

Ý tưởng thú vị ... nhưng bạn thường muốn đặt _idgiá trị cho trước, thay vì để MongoDB tạo một giá trị khác.
Dan Dascalescu

0

Bạn cũng có thể tạo một tài liệu mới từ la bàn MongoDB hoặc sử dụng lệnh và đặt specific _idgiá trị mà bạn muốn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.