MongoDB - อัปเดตวัตถุในอาร์เรย์ของเอกสาร (การอัปเดตที่ซ้อนกัน)


204

สมมติว่าเรามีคอลเล็กชันต่อไปนี้ซึ่งฉันมีคำถามสองสามข้อเกี่ยวกับ:

{
    "_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 - ฉันต้องการเพิ่มราคาสำหรับ "item_name": "my_item_two" และหากไม่มีอยู่ก็ควรผนวกเข้ากับอาร์เรย์ "items"

2 - ฉันจะอัปเดตสองฟิลด์พร้อมกันได้อย่างไร ตัวอย่างเช่นเพิ่มราคาสำหรับ "my_item_three" และในเวลาเดียวกันเพิ่ม "ผลรวม" (ด้วยค่าเดียวกัน)

ฉันชอบที่จะทำสิ่งนี้ในด้าน MongoDB มิฉะนั้นฉันต้องโหลดเอกสารในฝั่งไคลเอ็นต์ (Python) และสร้างเอกสารที่อัปเดตแล้วแทนที่ด้วยเอกสารที่มีอยู่ใน MongoDB

UPDATE นี่คือสิ่งที่ฉันได้ลองและทำงานได้ดีถ้า Object มีอยู่ :

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

แต่ถ้าไม่มีกุญแจมันก็ไม่ทำอะไรเลย นอกจากนี้มันจะอัพเดตวัตถุที่ซ้อนกันเท่านั้น ไม่มีวิธีใดที่คำสั่งนี้จะอัปเดตฟิลด์ "รวม" เช่นกัน


1
ฉันคิดว่าคุณไม่สามารถทำสิ่งนี้ได้ใน Mongo ยกเว้นอาจจะมีความเจ็บปวดมากในการใช้ eval Mongo มีข้อ จำกัด ในตัวเลือกข้อมูล
Antti Haapala

3
@Haapala: mongodb มี $ inc และอัปเดตด้วย upsert
jdi

1
@ jdi ใช่แล้ว แต่มันไม่ได้ช่วยอะไรมากมาย แต่สิ่งที่เขาต้องการคือหลาย ๆ $ incs ตามเงื่อนไขและหากไม่มีรายการอยู่ก็จำเป็นต้องใช้ $ push
Antti Haapala

คำตอบ:


237

สำหรับคำถามที่ 1 เรามาแบ่งมันเป็นสองส่วน ก่อนอื่นให้เพิ่มเอกสารใด ๆ ที่มี "items.item_name" เท่ากับ "my_item_two" สำหรับสิ่งนี้คุณจะต้องใช้โอเปอเรเตอร์ "$" ตำแหน่ง สิ่งที่ต้องการ:

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

โปรดทราบว่าสิ่งนี้จะเพิ่มเฉพาะเอกสารย่อยแรกที่ตรงกันในอาร์เรย์ใด ๆ (ดังนั้นหากคุณมีเอกสารอื่นในอาร์เรย์ที่มี "item_name" เท่ากับ "my_item_two" จะไม่ได้รับการเพิ่มขึ้น) แต่นี่อาจเป็นสิ่งที่คุณต้องการ

ส่วนที่สองนั้นยากกว่า เราสามารถผลักรายการใหม่ไปยังอาร์เรย์โดยไม่มี "my_item_two" ดังนี้:

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

สำหรับคำถามที่ 2 ของคุณคำตอบนั้นง่ายกว่า หากต้องการเพิ่มยอดรวมและราคาของ item_three ในเอกสารใด ๆ ที่มี "my_item_three" คุณสามารถใช้ตัวดำเนินการ $ inc ในหลาย ๆ เขตข้อมูลในเวลาเดียวกัน สิ่งที่ต้องการ:

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

ขอบคุณสำหรับการตอบกลับ. คำตอบสำหรับคำถาม # 2 นั้นสมบูรณ์แบบและใช้ได้ดี :) แต่สำหรับคำถาม # 1 ปัญหาคือคุณควรค้นหาเอกสารด้วย "user_id" และไม่ใช่โดย "items.item_name" ในกรณีนี้ฉันไม่สามารถใช้ตัวดำเนินการตำแหน่งได้เนื่องจากฉันไม่ได้ระบุอาร์เรย์ในคำค้นหา นอกจากนี้ฉันต้องการให้ทั้งสองส่วนทำในนัดเดียว (ถ้าเป็นไปได้)
Majid

ตัวอย่างที่ดี ฉันรู้สึกว่าฉันกำลังทำให้ทิศทางนี้ชัดเจนจากลิงก์ในคำตอบของฉัน แต่ฉันก็ยังได้รับการต่อต้านโดยบอกว่าไม่ใช่สิ่งที่เขากำลังมองหา เดา OP จำเป็นต้องมีเพื่อดูตัวอย่างเกี่ยวกับการทำหลาย $ inc
jdi

สำหรับคำถาม # 1 คุณควรจะสามารถทำได้สองส่วนด้วยการเพิ่ม user_id ลงในคิวรีแบบสอบถามของการอัปเดตแต่ละรายการ (ฉันจะแก้ไขคำตอบตามนั้น) จะต้องคิดให้มากขึ้นว่าจะทำอะไรได้บ้างในนัดเดียว
matulef

6
พารามิเตอร์ที่ 3 และ 4 ในวิธีการอัพเดตคืออะไร? และทำไม?
skmahawar

5
@skmahawar เกี่ยวกับ params ที่ 3 และ 4, docs.mongodb.com/manual/reference/method/db.collection.updateบ่งชี้ว่าตัวเลือกเหล่านี้มีไว้สำหรับ "upsert" และ "multi" ตามลำดับ สำหรับ upsert หากตั้งค่าเป็นจริงให้สร้างเอกสารใหม่เมื่อไม่มีเอกสารที่ตรงกับเกณฑ์การสืบค้น ค่าเริ่มต้นคือ false ซึ่งไม่ได้แทรกเอกสารใหม่เมื่อไม่พบการจับคู่ "สำหรับหลายรายการหากตั้งค่าเป็นจริงอัปเดตหลายเอกสารที่ตรงกับเกณฑ์การค้นหาหากตั้งค่าเป็นเท็จอัพเดตหนึ่งเอกสารค่าเริ่มต้นคือ false.
Mike Strand

24

ไม่มีวิธีการทำเช่นนี้ในแบบสอบถามเดียว คุณต้องค้นหาเอกสารในแบบสอบถามแรก:

ถ้าเอกสารมีอยู่:

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

อื่น

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

{$ne : "my_item_two" }ไม่จำเป็นต้องเพิ่มสภาพ

นอกจากนี้ในการตั้งค่าแบบหลายเธรดคุณต้องระวังว่ามีเพียงหนึ่งเธรดเท่านั้นที่สามารถเรียกใช้งานที่สอง (แทรกกรณีหากไม่พบเอกสาร) ในแต่ละครั้งมิฉะนั้นจะแทรกเอกสารฝังที่ซ้ำกัน


1
ไม่มีวิธีการทำเช่นนี้ในแบบสอบถามเดียวดูด้านบน
Martijn Scheffer

1
@MartijnScheffer ฉันไม่เห็นวิธีการทำเช่นนี้เป็นแบบสอบถามเดียวที่ใดก็ได้ในหัวข้อนี้ แม้แต่ "คำตอบ" ก็ยังใช้การค้นหา 2 ครั้งเหมือนกับในคำตอบนี้ ฉันพลาดอะไรไปรึเปล่า? ฉันยังหวังว่าจะมีวิธีการทำเช่นนี้ในการค้นหาเดียวเพื่อป้องกันปัญหาเธรดหลาย ๆ
dferraro

@Udit ฉันจะใช้รายการได้อย่างไรราคา $. ภายในเงื่อนไข เมื่อฉันพยายามเพิ่มเงื่อนไขเช่น "การเพิ่มขึ้นเฉพาะในกรณีที่ราคามากกว่า 0" มันไม่ทำงาน - โดยทั่วไปไม่มีการปรับปรุงใด ๆ ฉันสามารถใช้สิ่งนี้ในสภาพที่อยู่หรือฉันหายไปบางอย่าง? รายการ. $. price: {$ gt: 0}
dferraro

บางสิ่งบางอย่างต้องหายไป :)
Martijn Scheffer

3

เราสามารถใช้$setโอเปอเรเตอร์เพื่ออัปเดตอาร์เรย์ที่ซ้อนกันภายในวัตถุที่ยื่นเพื่ออัปเดตค่า

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.