วิธีการอัปเดตองค์ประกอบหลายแถวใน MongoDB


183

ฉันมีเอกสาร Mongo ที่มีองค์ประกอบมากมาย

ฉันต้องการรีเซ็ต.handledคุณลักษณะของวัตถุทั้งหมดในอาร์เรย์โดยที่.profile= XX

เอกสารอยู่ในรูปแบบต่อไปนี้:

{
    "_id": ObjectId("4d2d8deff4e6c1d71fc29a07"),
    "user_id": "714638ba-2e08-2168-2b99-00002f3d43c0",
    "events": [{
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 20,
            "data": "....."
        }
        ...
    ]
}

ดังนั้นฉันลองต่อไปนี้:

.update({"events.profile":10},{$set:{"events.$.handled":0}},false,true)

อย่างไรก็ตามมันจะอัพเดทเฉพาะองค์ประกอบอาเรย์แรกที่ตรงกันในแต่ละเอกสาร (นั่นเป็นพฤติกรรมที่กำหนดไว้สำหรับ$ - ผู้ประกอบการตำแหน่ง )

ฉันจะอัปเดตองค์ประกอบอาร์เรย์ที่ตรงกันทั้งหมดได้อย่างไร


2
การอัปเดตเซ็ตย่อยหรือไอเท็มอาเรย์ทั้งหมดถูกเพิ่มเข้าไปใน mongodb 3.6: docs.mongodb.com/manual/reference/operator/update/ ......
Jaap

อย่าลืมตรวจสอบ arrayFilters และพิจารณาว่าจะใช้แบบสอบถามใดเพื่อทำให้การอัปเดตมีประสิทธิภาพ ตรวจสอบคำตอบโดย Neil Lunn: stackoverflow.com/a/46054172/337401
Jaap

ตรวจสอบคำตอบของฉัน
46419

คำตอบ:


111

ในขณะนี้ไม่สามารถใช้โอเปอเรเตอร์ตำแหน่งเพื่ออัปเดตรายการทั้งหมดในอาร์เรย์ได้ ดู JIRA http://jira.mongodb.org/browse/SERVER-1243

ในการหลีกเลี่ยงคุณสามารถ:

  • อัปเดตแต่ละรายการทีละรายการ (events.0.handled events.1.handled ... ) หรือ ...
  • อ่านเอกสารทำการแก้ไขด้วยตนเองและบันทึกไว้แทนที่อันเก่า (ทำเครื่องหมาย"อัปเดตหากปัจจุบัน"หากคุณต้องการมั่นใจว่าการอัปเดตของอะตอมมิก)

15
หากคุณมีปัญหาที่คล้ายกันให้ลงคะแนนสำหรับปัญหานี้ - jira.mongodb.org/browse/SERVER-1243
LiorH

ฉันชอบอ่านเอกสารและบันทึกแนวทาง แต่ผมใช้ที่นอนก่อน Mongo ดังนั้นวิธีการที่ดูเหมือนว่าธรรมชาติมากขึ้นเนื่องจากไม่มี API แบบสอบถามสำหรับที่นอนเพียง API REST สำหรับเอกสารทั้งหมด
อดัม

1
ทั้งสองวิธีนี้ต้องการหน่วยความจำค่อนข้างสูงใช่ไหม หากมีเอกสารจำนวนมากที่ต้องค้นหาและต้องโหลดทั้งหมด (หรืออาร์เรย์ที่ซ้อนกัน) เพื่ออัปเดต ... + ยังมีปัญหาเล็กน้อยในการใช้หากต้องทำแบบอะซิงโครนัส ...
Ixx

13
ปัญหาทางเทคนิคทั้งหมดที่นอกเหนือจากนั้นค่อนข้างน่าประหลาดใจที่ฟีเจอร์นี้ไม่สามารถใช้งานได้ใน MongoDB ข้อ จำกัด นี้ให้อิสระในการปรับแต่งสคีมาฐานข้อมูลไม่มาก
Sung Cho

5
Neil Lunn stackoverflow.com/a/46054172/337401ตอบคำถามนี้สำหรับรุ่น 3.6 เนื่องจากนี่เป็นคำถามที่ได้รับความนิยมอาจมีค่าในขณะที่อัปเดตคำตอบที่ยอมรับนี้พร้อมการอ้างอิงถึงคำตอบของ Neil Lunn
Jaap

74

ด้วยการเปิดตัว MongoDB 3.6 (และมีให้ในสาขาการพัฒนาจาก MongoDB 3.5.12) ตอนนี้คุณสามารถอัปเดตองค์ประกอบหลายชุดในคำขอเดียว

สิ่งนี้ใช้ไวยากรณ์ตัวดำเนินการอัพเดตตำแหน่งที่กรองแล้วซึ่ง$[<identifier>]นำมาใช้ในเวอร์ชันนี้:

db.collection.update(
  { "events.profile":10 },
  { "$set": { "events.$[elem].handled": 0 } },
  { "arrayFilters": [{ "elem.profile": 10 }], "multi": true }
)

"arrayFilters"เป็นส่งผ่านไปยังตัวเลือกสำหรับการ.update()หรือแม้กระทั่ง .updateOne(), .updateMany(), .findOneAndUpdate()หรือ.bulkWrite()วิธีการระบุเงื่อนไขให้ตรงกับตัวระบุที่กำหนดในคำสั่งปรับปรุง องค์ประกอบใด ๆ ที่ตรงกับเงื่อนไขที่กำหนดจะได้รับการอัปเดต

สังเกตว่าใช้"multi"ตามที่กำหนดไว้ในบริบทของคำถามในความคาดหมายว่าจะ "อัปเดตองค์ประกอบหลายรายการ" แต่นี่ไม่ใช่และยังไม่เป็นเช่นนั้น การใช้งานที่นี่ใช้กับ"เอกสารหลายฉบับ"ตามที่ได้รับเป็นกรณีหรือตอนนี้ระบุไว้เป็นอย่างอื่นเป็นการตั้งค่าบังคับของ.updateMany()ในรุ่น API ที่ทันสมัย

หมายเหตุค่อนข้างแดกดันเนื่องจากสิ่งนี้มีการระบุไว้ใน "ตัวเลือก" อาร์กิวเมนต์สำหรับ.update()และชอบวิธีการไวยากรณ์โดยทั่วไปเข้ากันได้กับทุกรุ่นไดร์เวอร์รุ่นล่าสุด

อย่างไรก็ตามสิ่งนี้ไม่เป็นความจริงของmongoเชลล์เนื่องจากวิธีการใช้งานที่นั่น ("แดกดันสำหรับความเข้ากันได้แบบย้อนหลัง") arrayFiltersอาร์กิวเมนต์ไม่ได้รับการยอมรับและลบออกโดยวิธีการภายในที่แยกวิเคราะห์ตัวเลือกเพื่อส่ง "ความเข้ากันได้ย้อนหลัง" รุ่นเซิร์ฟเวอร์ MongoDB และ.update()ไวยากรณ์การเรียก API "ดั้งเดิม"

ดังนั้นหากคุณต้องการใช้คำสั่งในmongoเชลล์หรือผลิตภัณฑ์ "shell based" อื่น ๆ (โดยเฉพาะอย่างยิ่ง Robo 3T) คุณจำเป็นต้องมีเวอร์ชันล่าสุดจากสาขาการพัฒนาหรือรีลีสการผลิตตั้งแต่ 3.6 ขึ้นไป

ดูเพิ่มเติมpositional all $[]ที่อัปเดต "องค์ประกอบอาร์เรย์หลายรายการ" แต่ไม่ต้องใช้กับเงื่อนไขที่ระบุและใช้กับองค์ประกอบทั้งหมดในอาร์เรย์ที่เป็นการกระทำที่ต้องการ

โปรดดูการอัปเดต Nested Array ด้วย MongoDBสำหรับวิธีการที่ตัวดำเนินการตำแหน่งใหม่เหล่านี้นำไปใช้กับโครงสร้างอาร์เรย์ "ซ้อนกัน" โดยที่ "อาร์เรย์อยู่ภายในอาร์เรย์อื่น"

สำคัญ - การติดตั้งที่อัปเกรดแล้วจากเวอร์ชั่นก่อนหน้า "อาจ" ไม่ได้เปิดใช้งานฟีเจอร์ MongoDB ซึ่งอาจทำให้คำสั่งล้มเหลว คุณควรตรวจสอบให้แน่ใจว่ากระบวนการอัปเกรดของคุณเสร็จสมบูรณ์พร้อมรายละเอียดเช่นการอัพเกรดดัชนีจากนั้นเรียกใช้

   db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } )

หรือรุ่นที่สูงกว่าตามที่ใช้กับเวอร์ชันที่คุณติดตั้ง เช่น"4.0"รุ่น 4 ขึ้นไปในปัจจุบัน คุณลักษณะนี้เปิดใช้งานเช่นตัวดำเนินการอัพเดตตำแหน่งใหม่และอื่น ๆ คุณสามารถตรวจสอบกับ:

   db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )

เพื่อกลับการตั้งค่าปัจจุบัน


10
คำตอบที่ยอมรับควรได้รับการอัปเดตและอ้างอิงถึงคำตอบนี้
Jaap

3
คือelemอะไร
user1063287

1
สิ่งนี้ถูกต้อง โปรดทราบว่า RoboMongo ยังไม่รองรับarrayFiltersดังนั้นให้เรียกใช้อัปเดตผ่าน CLI stackoverflow.com/questions/48322834/…
drlff

ขอบคุณ Neil โดยเฉพาะอย่างยิ่งในส่วนที่สำคัญสิ่งที่ฉันต้องการ
janfabian

รหัสนี้ส่งกลับข้อผิดพลาดใน pymongo มีข้อผิดพลาด: เพิ่ม TypeError ("% s ต้องเป็นจริงหรือเท็จ"% (ตัวเลือก,)) TypeError: upsert ต้องเป็นจริงหรือเท็จ
Vagif

67

สิ่งที่ใช้ได้ผลสำหรับฉันคือ:

db.collection.find({ _id: ObjectId('4d2d8deff4e6c1d71fc29a07') })
  .forEach(function (doc) {
    doc.events.forEach(function (event) {
      if (event.profile === 10) {
        event.handled=0;
      }
    });
    db.collection.save(doc);
  });

ฉันคิดว่ามันชัดเจนสำหรับมือใหม่ mongo และทุกคนที่คุ้นเคยกับ JQuery และเพื่อน ๆ


ฉันกำลังใช้db.posts.find({ 'permalink':permalink }).forEach( function(doc) {...และฉันจะได้รับOops.. TypeError: Object # has no method 'forEach'
Squirrl

3
@Squirrl อาจเป็นรุ่น Mongodb ที่ล้าสมัยหรือไม่ เอกสารมีความชัดเจนในการใช้ฟังก์ชัน forEach กับเคอร์เซอร์ แต่ไม่ได้ระบุว่ารองรับเวอร์ชันใด docs.mongodb.org/manual/reference/method/cursor.forEach
Daniel Cerecedo

@Squirrl ลองdb.posts.find(...).toArray().forEach(...)
marmor

เราไม่สามารถทำได้โดยไม่ใช้Javascriptหรือไม่? ฉันต้องการทำการอัปเดตนี้โดยตรงจากเชลล์ mongo โดยไม่ต้องใช้ Javascript API
Meliodas

1
คุณช่วยเขียนแบบสอบถามนี้ในไดรเวอร์ mongodb สำหรับ java หรือกับ spring-data-mongodb ได้ไหม? ขอบคุณ kris
chiku

18

นอกจากนี้ยังสามารถทำได้ในขณะที่ลูปซึ่งจะตรวจสอบเพื่อดูว่าเอกสารใด ๆ ที่ยังคงมีเอกสารย่อยที่ยังไม่ได้รับการปรับปรุง วิธีนี้จะรักษา atomicity ของการอัปเดตของคุณ (ซึ่งโซลูชันอื่น ๆ ที่นี่ไม่ได้)

var query = {
    events: {
        $elemMatch: {
            profile: 10,
            handled: { $ne: 0 }
        }
    }
};

while (db.yourCollection.find(query).count() > 0) {
    db.yourCollection.update(
        query,
        { $set: { "events.$.handled": 0 } },
        { multi: true }
    );
}

จำนวนครั้งที่ลูปถูกดำเนินการจะเท่ากับจำนวนสูงสุดของเอกสารย่อยที่มีprofileค่าเท่ากับ 10 และhandledไม่เท่ากับ 0 เกิดขึ้นในเอกสารใด ๆ ในคอลเลกชันของคุณ ดังนั้นหากคุณมีเอกสาร 100 รายการในคอลเล็กชันของคุณและหนึ่งในนั้นมีเอกสารย่อยสามqueryฉบับที่ตรงกันและเอกสารอื่น ๆ ทั้งหมดมีเอกสารย่อยที่ตรงกันน้อยลงลูปจะดำเนินการสามครั้ง

วิธีนี้หลีกเลี่ยงอันตรายจากการอุดตันข้อมูลอื่น ๆ ที่อาจได้รับการปรับปรุงโดยกระบวนการอื่นในขณะที่สคริปต์นี้ทำงาน นอกจากนี้ยังลดจำนวนข้อมูลที่จะถ่ายโอนระหว่างไคลเอ็นต์และเซิร์ฟเวอร์


13

ในความเป็นจริงแล้วสิ่งนี้เกี่ยวข้องกับปัญหาที่ยืนยาวที่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 })
}

มีบางสิ่งที่คุณไม่ต้องการทำในการอัปเดต:

  1. อย่าอัพเดท "one shot" array:ที่ไหนถ้าคุณคิดว่ามันอาจจะมีประสิทธิภาพมากกว่าในการอัพเดทเนื้อหา array ทั้งหมดในโค้ดแล้วเพียงแค่$setarray ทั้งหมดในแต่ละเอกสาร สิ่งนี้อาจดูเหมือนจะดำเนินการได้เร็วขึ้น แต่ไม่รับประกันว่าเนื้อหาอาเรย์จะไม่เปลี่ยนแปลงเนื่องจากถูกอ่านและทำการอัปเดต แม้ว่า$setจะยังคงเป็นตัวดำเนินการแบบปรมาณู แต่ก็จะอัปเดตอาร์เรย์ด้วยสิ่งที่ "คิดว่า" เป็นข้อมูลที่ถูกต้องเท่านั้นและดังนั้นจึงมีแนวโน้มที่จะเขียนทับการเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นระหว่างการอ่านและการเขียน

  2. อย่าคำนวณค่าดัชนีเพื่ออัปเดต:ซึ่งคล้ายกับวิธี "one shot" ที่คุณเพิ่งจะหาว่าตำแหน่ง0และตำแหน่ง2(และอื่น ๆ ) เป็นองค์ประกอบในการอัปเดตและโค้ดเหล่านี้ด้วยและคำสั่งในที่สุดเช่น:

    { "$set": {
        "events.0.handled": 0,
        "events.2.handled": 0
    }}

    ปัญหาอีกครั้งที่นี่คือ "ข้อสันนิษฐาน" ที่ค่าดัชนีเหล่านั้นที่พบเมื่ออ่านเอกสารนั้นเป็นค่าดัชนีเดียวกันในแถวลำดับที่ ณ เวลาที่ทำการอัปเดต หากมีการเพิ่มรายการใหม่ลงในอาร์เรย์ในลักษณะที่เปลี่ยนแปลงคำสั่งซื้อตำแหน่งเหล่านั้นจะไม่สามารถใช้งานได้อีกต่อไปและรายการที่ไม่ถูกต้องจะถูกอัปเดตตามความเป็นจริง

ดังนั้นจนกว่าจะมีการกำหนดไวยากรณ์ที่เหมาะสมเพื่อให้สามารถประมวลผลองค์ประกอบอาร์เรย์ที่ตรงกันหลายรายการในคำสั่งการอัปเดตเดียวดังนั้นวิธีการพื้นฐานคือการอัปเดตองค์ประกอบอาร์เรย์ที่ตรงกันแต่ละรายการในคำสั่ง indvidual (ในอุดมคติเป็นกลุ่ม) หรือ เพื่ออัปเดตหรืออัปเดตต่อไปจนกว่าจะไม่มีการส่งคืนผลลัพธ์ที่ถูกแก้ไขอีก คุณควร "กำลัง" ประมวลผลการอัปเดตตำแหน่ง$ในองค์ประกอบอาร์เรย์ที่ตรงกันเสมอไม่ว่าคุณจะอัปเดตเพียงองค์ประกอบเดียวต่อคำสั่งก็ตาม

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


8

ฉันประหลาดใจที่เรื่องนี้ยังไม่ได้รับการแก้ไขในภาษา mongo มองโกลโดยรวมดูเหมือนจะไม่ยอดเยี่ยมเมื่อจัดการกับอาร์เรย์ย่อย ตัวอย่างเช่นคุณไม่สามารถนับอาร์เรย์ย่อยได้

ฉันใช้ทางออกแรกของ Javier อ่านอาร์เรย์ในกิจกรรมจากนั้นวนซ้ำและสร้างชุดประสบการณ์:

var set = {}, i, l;
for(i=0,l=events.length;i<l;i++) {
  if(events[i].profile == 10) {
    set['events.' + i + '.handled'] = 0;
  }
}

.update(objId, {$set:set});

สิ่งนี้สามารถแยกออกเป็นฟังก์ชันโดยใช้การเรียกกลับสำหรับการทดสอบตามเงื่อนไข


ขอบคุณสำหรับสิ่งนี้! ไม่อยากจะเชื่อเลยว่าคุณสมบัตินี้ยังไม่รองรับอย่างเป็นทางการ! ใช้สิ่งนี้เพื่อเพิ่มทุกรายการของ subarray สำหรับคนอื่น ๆ ที่อ่าน ... เพื่ออัปเดตทุกรายการเพียงแค่ลบคำสั่ง if
Zaheer

9
นี่ไม่ใช่ทางออกที่ปลอดภัย หากมีการเพิ่มระเบียนในขณะที่คุณเรียกใช้การอัปเดตคุณจะทำให้ข้อมูลของคุณเสียหาย
Merc

4

ฉันกำลังมองหาวิธีการแก้ปัญหานี้โดยใช้ไดรเวอร์ใหม่ล่าสุดสำหรับ C # 3.6 และนี่คือการแก้ไขที่ฉันตัดสินในที่สุด กุญแจที่นี่ใช้"$ []"ซึ่งตาม MongoDB นั้นใหม่ในเวอร์ชั่น 3.6 ดูhttps://docs.mongodb.com/manual/reference/operator/update/positional-all/#up S []สำหรับข้อมูลเพิ่มเติม

นี่คือรหัส:

{
   var filter = Builders<Scene>.Filter.Where(i => i.ID != null);
   var update = Builders<Scene>.Update.Unset("area.$[].discoveredBy");
   var result = collection.UpdateMany(filter, update, new UpdateOptions { IsUpsert = true});
}

สำหรับบริบทเพิ่มเติมดูโพสต์ต้นฉบับของฉันที่นี่: ลบองค์ประกอบอาร์เรย์จากเอกสารทั้งหมดโดยใช้ไดรเวอร์ MongoDB C #


4

เธรดนั้นเก่ามาก แต่ฉันมาหาคำตอบที่นี่ดังนั้นจึงให้โซลูชันใหม่

ด้วย MongoDB เวอร์ชั่น 3.6+ ตอนนี้คุณสามารถใช้โอเปอเรเตอร์ตำแหน่งเพื่ออัพเดตไอเท็มทั้งหมดในอาร์เรย์ ดูเอกสารอย่างเป็นทางการที่นี่

ข้อความค้นหาต่อไปนี้ใช้ได้กับคำถามที่ถามที่นี่ ฉันได้ตรวจสอบกับไดรเวอร์ Java-MongoDB แล้วและทำงานได้สำเร็จ

.update(   // or updateMany directly, removing the flag for 'multi'
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},  // notice the empty brackets after '$' opearor
   false,
   true
)

หวังว่านี่จะช่วยให้คนอย่างฉัน


1

ฉันลองต่อไปนี้และมันใช้งานได้ดี

.update({'events.profile': 10}, { '$set': {'events.$.handled': 0 }},{ safe: true, multi:true }, callback function);

// ฟังก์ชั่นการโทรกลับในกรณีของ nodejs


รหัสนี้เพียงแค่อัปเดตรายการแรกที่จับคู่ในอาร์เรย์ คำตอบที่ไม่ถูกต้อง.
Hamlett

มันใช้งานได้กับ v2.6 คุณอาจจะเป็นรุ่นที่เก่ากว่า? นี่คือเอกสารของdocs.mongodb.com/manual/reference/method/db.collection.update/...
Jialin Zou

1

คุณสามารถปรับปรุงองค์ประกอบทั้งหมดใน MongoDB

db.collectioname.updateOne(
{ "key": /vikas/i },
{ $set: { 
 "arr.$[].status" : "completed"
} }
)

มันจะอัปเดตค่า "สถานะ" ทั้งหมดเป็น "เสร็จสิ้น" ใน "arr" Array

หากเอกสารเดียวเท่านั้น

db.collectioname.updateOne(
 { key:"someunique", "arr.key": "myuniq" },
 { $set: { 
   "arr.$.status" : "completed", 
   "arr.$.msgs":  {
                "result" : ""
        }
   
 } }
)

แต่ถ้าไม่ใช่และคุณไม่ต้องการให้เอกสารทั้งหมดในอาร์เรย์อัปเดตคุณต้องวนซ้ำองค์ประกอบและข้างใน if block

db.collectioname.find({findCriteria })
  .forEach(function (doc) {
    doc.arr.forEach(function (singlearr) {
      if (singlearr check) {
        singlearr.handled =0
      }
    });
    db.collection.save(doc);
  });

0

ที่จริงแล้วคำสั่งบันทึกจะอยู่ในอินสแตนซ์ของคลาสเอกสารเท่านั้น มีวิธีและแอตทริบิวต์มากมาย ดังนั้นคุณสามารถใช้ฟังก์ชันlean ()เพื่อลดภาระงาน อ้างอิงที่นี่ https://hashnode.com/post/why-are-mongoose-mongodb-odm-lean-queries-faster-than-normal-queries-cillvawhq0062kj53asxoyn7j

ปัญหาอีกประการหนึ่งของฟังก์ชั่นการบันทึกที่จะทำให้ข้อมูลขัดแย้งกับการประหยัดหลายอย่างในเวลาเดียวกัน รุ่นอัพเดทจะทำให้ข้อมูลอย่างสม่ำเสมอ ดังนั้นเพื่ออัปเดตหลายรายการในอาร์เรย์ของเอกสาร ใช้ภาษาการเขียนโปรแกรมที่คุณคุ้นเคยและลองสิ่งนี้ฉันใช้พังพอนในสิ่งนั้น:

User.findOne({'_id': '4d2d8deff4e6c1d71fc29a07'}).lean().exec()
  .then(usr =>{
    if(!usr)  return
    usr.events.forEach( e => {
      if(e && e.profile==10 ) e.handled = 0
    })
    User.findOneAndUpdate(
      {'_id': '4d2d8deff4e6c1d71fc29a07'},
      {$set: {events: usr.events}},
      {new: true}
    ).lean().exec().then(updatedUsr => console.log(updatedUsr))
})

0

ผู้ประกอบการ $ [] เลือกอาร์เรย์ที่ซ้อนกันทั้งหมด .. คุณสามารถอัปเดตรายการอาร์เรย์ทั้งหมดด้วย '$ []'

.update({"events.profile":10},{$set:{"events.$[].handled":0}},false,true)

การอ้างอิง


คุณช่วยอธิบายได้ไหมว่าทำไมคุณถึงรวม "เท็จจริง" ไว้ที่นี่? ฉันหามันไม่พบในเอกสาร
สัน

ตอบผิดตัวดำเนินการตำแหน่งทั้งหมด$[] เพียงอัปเดตฟิลด์ทั้งหมดในอาร์เรย์ที่ระบุ สิ่งที่ใช้ได้คือตัวดำเนินการตำแหน่งที่กรองแล้ว$[identifier]ซึ่งตัวดำเนินการบนเขตข้อมูลอาร์เรย์ที่ตรงกับเงื่อนไขที่ระบุ ควรใช้ร่วมกับarrayFilters Refrence: docs.mongodb.com/manual/release-notes/3.6/#arrayfiltersและdocs.mongodb.com/manual/reference/operator/update/ ......
Lawrence Eagles

0

โปรดระวังว่าบางคำตอบในกระทู้นี้แนะนำให้ใช้ $ [] ผิด

db.collection.update(
   {"events.profile":10},
   {$set:{"events.$[].handled":0}},
   {multi:true}
)

โค้ดด้านบนจะอัปเดต "จัดการ" เป็น 0 สำหรับองค์ประกอบทั้งหมดในอาร์เรย์ "events" โดยไม่คำนึงถึงค่า "โปรไฟล์" แบบสอบถาม{"events.profile":10}เป็นเพียงการกรองเอกสารทั้งหมดไม่ใช่เอกสารในอาร์เรย์ ในสถานการณ์เช่นนี้จะต้องใช้$[elem]กับarrayFiltersการระบุเงื่อนไขของรายการอาร์เรย์ดังนั้นคำตอบของนีลลุนน์ถูกต้อง


0

อัปเดตฟิลด์อาร์เรย์ในเอกสารหลายรายการใน Mongo db

ใช้ $ pull หรือ $ push พร้อมอัปเดตแบบสอบถามจำนวนมากเพื่ออัปเดตองค์ประกอบอาร์เรย์ใน mongoDb

Notification.updateMany(
    { "_id": { $in: req.body.notificationIds } },
    {
        $pull: { "receiversId": req.body.userId }
    }, function (err) {
        if (err) {
            res.status(500).json({ "msg": err });
        } else {
            res.status(200).json({
                "msg": "Notification Deleted Successfully."
            });
        }
    });

0

ขั้นแรก: รหัสของคุณไม่ทำงานเนื่องจากคุณใช้ตัวดำเนินการตำแหน่ง$ซึ่งระบุองค์ประกอบที่จะอัปเดตในอาเรย์เท่านั้น แต่ไม่ได้ระบุตำแหน่งของอาเรย์อย่างชัดเจน

$[<identifier>]สิ่งที่คุณต้องเป็นผู้ประกอบการที่ผ่านการกรองตำแหน่ง มันจะปรับปรุงองค์ประกอบทั้งหมดที่ตรงกับเงื่อนไขตัวกรองอาร์เรย์

สารละลาย:

db.collection.update({"events.profile":10}, { $set: { "events.$[elem].handled" : 0 } },
   {
     multi: true,
     arrayFilters: [ { "elem.profile": 10 } ]
})

เยี่ยมชม mongodb doc ที่นี่

รหัสทำอะไร:

  1. {"events.profile":10} กรองคอลเล็กชันของคุณและส่งคืนเอกสารที่ตรงกับตัวกรอง

  2. ตัว$setดำเนินการอัพเดต: แก้ไขฟิลด์ที่ตรงกันของเอกสารที่ทำงาน

  3. {multi:true}มันทำให้การ.update()แก้ไขเอกสารทั้งหมดที่ตรงกับตัวกรองจึงทำงานเช่นupdateMany()

  4. { "events.$[elem].handled" : 0 } and arrayFilters: [ { "elem.profile": 10 } ] เทคนิคนี้เกี่ยวข้องกับการใช้อาร์เรย์ตำแหน่งที่ถูกกรองด้วย arrayFilters อาร์เรย์ตำแหน่งที่ถูกกรองที่นี่$[elem]ทำหน้าที่เป็นตัวยึดตำแหน่งสำหรับองค์ประกอบทั้งหมดในฟิลด์อาร์เรย์ที่ตรงกับเงื่อนไขที่ระบุในตัวกรองอาร์เรย์

ตัวกรองอาร์เรย์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.