MongoDB - Cập nhật các đối tượng trong mảng tài liệu (cập nhật lồng nhau)


204

Giả sử chúng tôi có bộ sưu tập sau đây, mà tôi có vài câu hỏi về:

{
    "_id" : ObjectId("4faaba123412d654fe83hg876"),
    "user_id" : 123456,
    "total" : 100,
    "items" : [
            {
                    "item_name" : "my_item_one",
                    "price" : 20
            },
            {
                    "item_name" : "my_item_two",
                    "price" : 50
            },
            {
                    "item_name" : "my_item_three",
                    "price" : 30
            }
    ]
}

1 - Tôi muốn tăng giá cho "item_name": "my_item_two" và nếu nó không tồn tại , nó nên được thêm vào mảng "vật phẩm".

2 - Làm cách nào tôi có thể cập nhật hai trường cùng một lúc. Ví dụ: tăng giá cho "my_item_three" và đồng thời tăng "tổng" (có cùng giá trị).

Tôi thích làm điều này ở phía MongoDB, nếu không tôi phải tải tài liệu ở phía máy khách (Python) và xây dựng tài liệu cập nhật và thay thế nó bằng tài liệu hiện có trong MongoDB.

CẬP NHẬT Đây là những gì tôi đã cố gắng và hoạt động tốt NẾU Đối tượng tồn tại :

db.test_invoice.update({user_id : 123456 , "items.item_name":"my_item_one"} , {$inc: {"items.$.price": 10}})

Nhưng nếu khóa không tồn tại thì không có gì. Ngoài ra nó chỉ cập nhật các đối tượng lồng nhau. Không có cách nào với lệnh này để cập nhật trường "tổng".


1
Tôi nghĩ rằng bạn không thể làm điều này trong mongo, ngoại trừ có thể đau đớn khi sử dụng eval. Mongo rất hạn chế trong dữ liệu ops.
Antti Haapala

3
@Haapala: MongoDB có $ inc và cập nhật với upsert
JDI

1
@jdi có, nhưng nó không giúp được gì nhiều ở đây, nhưng cái anh ta cần là nhiều $ incs, theo điều kiện, và nếu vật phẩm không tồn tại, thì cần đẩy $.
Antti Haapala

Câu trả lời:


237

Đối với câu hỏi số 1, hãy chia nó thành hai phần. Đầu tiên, tăng bất kỳ tài liệu nào có "items.item_name" bằng "my_item_two". Để làm điều này, bạn sẽ phải sử dụng toán tử "$" theo vị trí. Cái gì đó như:

 db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } , 
                {$inc : {"items.$.price" : 1} } , 
                false , 
                true);

Lưu ý rằng điều này sẽ chỉ tăng văn bản con phù hợp đầu tiên trong bất kỳ mảng nào (vì vậy nếu bạn có một tài liệu khác trong mảng có "item_name" bằng "my_item_two", nó sẽ không được tăng lên). Nhưng đây có thể là những gì bạn muốn.

Phần thứ hai là khó khăn hơn. Chúng ta có thể đẩy một mục mới vào một mảng mà không có "my_item_two" như sau:

 db.bar.update( {user_id : 123456, "items.item_name" : {$ne : "my_item_two" }} , 
                {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
                false , 
                true);

Đối với câu hỏi số 2 của bạn, câu trả lời dễ dàng hơn. Để tăng tổng và giá của item_three trong bất kỳ tài liệu nào có chứa "my_item_three", bạn có thể sử dụng toán tử $ inc trên nhiều trường cùng một lúc. Cái gì đó như:

db.bar.update( {"items.item_name" : {$ne : "my_item_three" }} ,
               {$inc : {total : 1 , "items.$.price" : 1}} ,
               false ,
               true);

Cảm ơn vi đa trả lơi. Câu trả lời cho Câu hỏi số 2 là hoàn hảo và hoạt động tốt :) Nhưng đối với Câu hỏi số 1, vấn đề là bạn nên tìm kiếm tài liệu bằng "user_id", chứ không phải bởi "items.item_name". Trong trường hợp này, tôi không thể sử dụng Toán tử vị trí, vì tôi chưa chỉ định mảng trong truy vấn tìm kiếm. Ngoài ra tôi muốn cả hai phần được thực hiện trong một shot. (nếu có thể)
Majid

Ví dụ tốt đẹp. Tôi cảm thấy như tôi đang làm cho hướng này rõ ràng từ các liên kết trong câu trả lời của tôi, nhưng tôi tiếp tục bị kháng cự nói rằng đó không phải là những gì anh ta đang tìm kiếm. Đoán OP chỉ cần xem các ví dụ về cách thực hiện nhiều $ inc.
JDI

Đối với câu hỏi số 1, bạn vẫn có thể thực hiện thành hai phần bằng cách thêm user_id vào bit truy vấn của mỗi bản cập nhật (Tôi sẽ sửa đổi câu trả lời tương ứng). Sẽ phải suy nghĩ nhiều hơn về việc liệu có thể thực hiện trong một lần chụp hay không.
matulef

6
Các tham số thứ 3 và thứ 4 trong phương pháp cập nhật là gì? và tại sao?
skmahawar 27/2/2015

5
@skmahawar liên quan đến thông số thứ 3 và thứ 4, docs.mongodb.com/manual/reference/method/db.collection.update chỉ ra các tùy chọn này lần lượt là "upsert" và "multi". Đối với upert, nếu được đặt thành true, sẽ tạo một tài liệu mới khi không có tài liệu nào khớp với tiêu chí truy vấn. Giá trị mặc định là false, không chèn tài liệu mới khi không tìm thấy kết quả khớp. "Đối với đa, Nếu được đặt thành true, cập nhật nhiều tài liệu đáp ứng tiêu chí truy vấn. Nếu được đặt thành false, cập nhật một tài liệu. Giá trị mặc định là sai.
Mike Strand

24

Không có cách nào để làm điều này trong một truy vấn duy nhất. Bạn phải tìm kiếm tài liệu trong truy vấn đầu tiên:

Nếu tài liệu tồn tại:

db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } , 
                {$inc : {"items.$.price" : 1} } , 
                false , 
                true);

Khác

db.bar.update( {user_id : 123456 } , 
                {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
                false , 
                true);

Không cần thêm điều kiện {$ne : "my_item_two" }.

Ngoài ra, trong môi trường đa luồng, bạn phải cẩn thận rằng chỉ một luồng có thể thực thi lần thứ hai (trường hợp chèn, nếu không tìm thấy tài liệu) tại một thời điểm, nếu không các tài liệu nhúng trùng lặp sẽ được chèn.


1
không, có một cách để làm điều này trong một truy vấn duy nhất, xem ở trên
Martijn Scheffer

1
@MartijnScheffer, tôi không thấy một cách để làm điều này như một truy vấn duy nhất ở bất cứ đâu trong chuỗi này. Ngay cả "câu trả lời" cũng đang sử dụng 2 truy vấn giống như trong câu trả lời này. Tui bỏ lỡ điều gì vậy? Tôi cũng hy vọng có một cách để làm điều này trong một truy vấn để ngăn chặn mọi sự cố đa luồng
dferraro

@Udit, làm thế nào tôi có thể sử dụng các mặt hàng. $. Giá trong điều kiện? khi tôi cố gắng thêm một điều kiện như "chỉ tăng nếu giá lớn hơn 0", thì nó không hoạt động - về cơ bản không có gì được cập nhật. Tôi có thể sử dụng cái này trong điều kiện không, hay tôi đang thiếu thứ gì? các mặt hàng. $. price: {$ gt: 0}
dferraro

một cái gì đó phải biến mất :)
Martijn Scheffer

3

Chúng ta có thể sử dụng $settoán tử để cập nhật mảng lồng bên trong đối tượng được đệ trình cập nhật giá trị

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
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.