ในความเป็นจริงแล้วสิ่งนี้เกี่ยวข้องกับปัญหาที่ยืนยาวที่http://jira.mongodb.org/browse/SERVER-1243ซึ่งในความเป็นจริงมีความท้าทายหลายประการที่จะเกิดขึ้นกับไวยากรณ์ที่ชัดเจนที่รองรับ พบ มีวิธีการในความเป็นจริงแล้วในสถานที่ที่ "ช่วยเหลือ" ในการแก้ปัญหานี้เช่นการดำเนินงานจำนวนมากซึ่งได้รับการดำเนินการหลังจากโพสต์ต้นฉบับนี้
ยังคงเป็นไปไม่ได้ที่จะอัปเดตองค์ประกอบอาร์เรย์ที่จับคู่มากกว่าหนึ่งรายการในคำสั่งอัปเดตเดียวดังนั้นแม้จะมีการอัปเดต "หลาย" ทั้งหมดที่คุณจะสามารถอัปเดตได้เป็นเพียงองค์ประกอบทางคณิตศาสตร์หนึ่งรายการในอาร์เรย์สำหรับเอกสารแต่ละชุด คำให้การ.
ทางออกที่ดีที่สุดที่เป็นไปได้ในปัจจุบันคือการค้นหาและวนลูปเอกสารที่ตรงกันทั้งหมดและประมวลผลการอัปเดตจำนวนมากซึ่งอย่างน้อยจะช่วยให้การดำเนินการจำนวนมากถูกส่งไปในคำขอเดียวด้วยการตอบสนองเอกพจน์ คุณสามารถเลือกที่.aggregate()
จะใช้เพื่อลดเนื้อหาของอาร์เรย์ที่ส่งคืนในผลลัพธ์การค้นหาให้เหลือเฉพาะเนื้อหาที่ตรงกับเงื่อนไขสำหรับการเลือกการอัปเดต:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$setDifference": [
{ "$map": {
"input": "$events",
"as": "event",
"in": {
"$cond": [
{ "$eq": [ "$$event.handled", 1 ] },
"$$el",
false
]
}
}},
[false]
]
}
}}
]).forEach(function(doc) {
doc.events.forEach(function(event) {
bulk.find({ "_id": doc._id, "events.handled": 1 }).updateOne({
"$set": { "events.$.handled": 0 }
});
count++;
if ( count % 1000 == 0 ) {
bulk.execute();
bulk = db.collection.initializeOrderedBulkOp();
}
});
});
if ( count % 1000 != 0 )
bulk.execute();
.aggregate()
ส่วนจะมีการทำงานเมื่อมีการระบุที่ "พิเศษ" สำหรับอาร์เรย์หรือเนื้อหาทั้งหมดสำหรับแต่ละองค์ประกอบรูปแบบ "พิเศษ" องค์ประกอบของตัวเอง นี่เป็นเพราะตัวดำเนินการ "set" ที่$setDifference
ใช้ในการกรองfalse
ค่าใด ๆ ที่ส่งคืนจากการ$map
ดำเนินการที่ใช้ในการประมวลผลอาร์เรย์สำหรับการจับคู่
หากเนื้อหาอาเรย์ของคุณไม่มีองค์ประกอบที่ไม่ซ้ำคุณสามารถลองวิธีอื่นด้วย$redact
:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
ในกรณีที่มีข้อ จำกัด คือถ้า "จัดการ" ในความเป็นจริงแล้วฟิลด์ที่จะปรากฏในระดับเอกสารอื่น ๆ นั้นคุณมีแนวโน้มที่จะได้รับผลลัพธ์ที่ไม่ได้ตรวจสอบ แต่ก็ดีเมื่อฟิลด์นั้นปรากฏในตำแหน่งเอกสารเพียงตำแหน่งเดียวเท่านั้น
การเผยแพร่ในอนาคต (โพสต์ 3.1 MongoDB) ณ การเขียนจะมีการ$filter
ดำเนินการที่เรียบง่าย
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$filter": {
"input": "$events",
"as": "event",
"cond": { "$eq": [ "$$event.handled", 1 ] }
}
}
}}
])
และทุกรุ่นที่รองรับ.aggregate()
สามารถใช้วิธีการต่อไปนี้ด้วย$unwind
แต่การใช้ตัวดำเนินการนั้นทำให้เป็นวิธีที่มีประสิทธิภาพน้อยที่สุดเนื่องจากการขยายอาร์เรย์ในไพพ์ไลน์:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$unwind": "$events" },
{ "$match": { "events.handled": 1 } },
{ "$group": {
"_id": "$_id",
"events": { "$push": "$events" }
}}
])
ในทุกกรณีที่เวอร์ชั่น MongoDB รองรับ "เคอร์เซอร์" จากเอาต์พุตแบบรวมนี่เป็นเพียงเรื่องของการเลือกวิธีการแล้ววนซ้ำผลลัพธ์ด้วยบล็อกของรหัสเดียวกันที่แสดงเพื่อประมวลผลคำสั่งการอัพเดทแบบกลุ่ม การดำเนินงานจำนวนมากและ "เคอร์เซอร์" จากผลลัพธ์รวมจะถูกนำเสนอในเวอร์ชันเดียวกัน (MongoDB 2.6) ดังนั้นโดยทั่วไปแล้วจะทำงานร่วมกันเพื่อการประมวลผล
ในรุ่นก่อนหน้านั้นอาจจะเป็นการดีที่สุดที่จะใช้เพียงแค่.find()
คืนค่าเคอร์เซอร์และกรองการประมวลผลคำสั่งให้เท่ากับจำนวนครั้งที่องค์ประกอบอาร์เรย์จับคู่กับการ.update()
วนซ้ำ:
db.collection.find({ "events.handled": 1 }).forEach(function(doc){
doc.events.filter(function(event){ return event.handled == 1 }).forEach(function(event){
db.collection.update({ "_id": doc._id },{ "$set": { "events.$.handled": 0 }});
});
});
หากคุณตั้งใจที่จะทำการอัปเดต "หลายคน" หรือเห็นว่ามีประสิทธิภาพมากกว่าการประมวลผลอัปเดตหลายรายการสำหรับเอกสารที่ตรงกันแต่ละชุดคุณสามารถกำหนดจำนวนสูงสุดของการจับคู่อาร์เรย์ที่เป็นไปได้เสมอและทำการอัปเดต "หลาย" ครั้งจนกระทั่งโดยทั่วไปไม่มีเอกสารเพิ่มเติมให้อัปเดต
วิธีการที่ถูกต้องสำหรับ MongoDB 2.4 และ 2.2 เวอร์ชันสามารถใช้.aggregate()
เพื่อค้นหาค่านี้:
var result = db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$unwind": "$events" },
{ "$match": { "events.handled": 1 } },
{ "$group": {
"_id": "$_id",
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": null,
"count": { "$max": "$count" }
}}
]);
var max = result.result[0].count;
while ( max-- ) {
db.collection.update({ "events.handled": 1},{ "$set": { "events.$.handled": 0 }},{ "multi": true })
}
มีบางสิ่งที่คุณไม่ต้องการทำในการอัปเดต:
อย่าอัพเดท "one shot" array:ที่ไหนถ้าคุณคิดว่ามันอาจจะมีประสิทธิภาพมากกว่าในการอัพเดทเนื้อหา array ทั้งหมดในโค้ดแล้วเพียงแค่$set
array ทั้งหมดในแต่ละเอกสาร สิ่งนี้อาจดูเหมือนจะดำเนินการได้เร็วขึ้น แต่ไม่รับประกันว่าเนื้อหาอาเรย์จะไม่เปลี่ยนแปลงเนื่องจากถูกอ่านและทำการอัปเดต แม้ว่า$set
จะยังคงเป็นตัวดำเนินการแบบปรมาณู แต่ก็จะอัปเดตอาร์เรย์ด้วยสิ่งที่ "คิดว่า" เป็นข้อมูลที่ถูกต้องเท่านั้นและดังนั้นจึงมีแนวโน้มที่จะเขียนทับการเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นระหว่างการอ่านและการเขียน
อย่าคำนวณค่าดัชนีเพื่ออัปเดต:ซึ่งคล้ายกับวิธี "one shot" ที่คุณเพิ่งจะหาว่าตำแหน่ง0
และตำแหน่ง2
(และอื่น ๆ ) เป็นองค์ประกอบในการอัปเดตและโค้ดเหล่านี้ด้วยและคำสั่งในที่สุดเช่น:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
ปัญหาอีกครั้งที่นี่คือ "ข้อสันนิษฐาน" ที่ค่าดัชนีเหล่านั้นที่พบเมื่ออ่านเอกสารนั้นเป็นค่าดัชนีเดียวกันในแถวลำดับที่ ณ เวลาที่ทำการอัปเดต หากมีการเพิ่มรายการใหม่ลงในอาร์เรย์ในลักษณะที่เปลี่ยนแปลงคำสั่งซื้อตำแหน่งเหล่านั้นจะไม่สามารถใช้งานได้อีกต่อไปและรายการที่ไม่ถูกต้องจะถูกอัปเดตตามความเป็นจริง
ดังนั้นจนกว่าจะมีการกำหนดไวยากรณ์ที่เหมาะสมเพื่อให้สามารถประมวลผลองค์ประกอบอาร์เรย์ที่ตรงกันหลายรายการในคำสั่งการอัปเดตเดียวดังนั้นวิธีการพื้นฐานคือการอัปเดตองค์ประกอบอาร์เรย์ที่ตรงกันแต่ละรายการในคำสั่ง indvidual (ในอุดมคติเป็นกลุ่ม) หรือ เพื่ออัปเดตหรืออัปเดตต่อไปจนกว่าจะไม่มีการส่งคืนผลลัพธ์ที่ถูกแก้ไขอีก คุณควร "กำลัง" ประมวลผลการอัปเดตตำแหน่ง$
ในองค์ประกอบอาร์เรย์ที่ตรงกันเสมอไม่ว่าคุณจะอัปเดตเพียงองค์ประกอบเดียวต่อคำสั่งก็ตาม
การดำเนินงานจำนวนมากเป็นวิธีการแก้ปัญหา "ทั่วไป" ในการประมวลผลการดำเนินการใด ๆ ที่เป็น "การดำเนินการหลายอย่าง" และเนื่องจากมีแอปพลิเคชันมากกว่านี้เพียงแค่อัปเดตองค์ประกอบอาร์เรย์หลายตัวด้วยค่าเดียวกัน และเป็นวิธีที่ดีที่สุดในการแก้ไขปัญหานี้