ฉันจะปรับปรุงวัตถุใน MongoDB บางส่วนเพื่อให้วัตถุใหม่จะซ้อนทับ / ผสานกับวัตถุที่มีอยู่


183

รับเอกสารนี้บันทึกใน MongoDB

{
   _id : ...,
   some_key: { 
        param1 : "val1",
        param2 : "val2",
        param3 : "val3"
   }
}

วัตถุที่มีข้อมูลใหม่ในparam2และparam3นอกโลกจะต้องได้รับการบันทึก

var new_info = {
    param2 : "val2_new",
    param3 : "val3_new"
};

ฉันต้องการผสาน / วางทับฟิลด์ใหม่เหนือสถานะที่มีอยู่ของวัตถุเพื่อให้ param1 ไม่ถูกลบออก

ทำสิ่งนี้

db.collection.update(  { _id:...} , { $set: { some_key : new_info  } } 

จะนำไปสู่ ​​MongoDB กำลังทำตามที่ขอและตั้งค่า some_key ให้เป็นค่านั้น แทนที่เก่า

{
   _id : ...,
   some_key: { 
      param2 : "val2_new",
      param3 : "val3_new"
   }
}

มีวิธีการอย่างไรที่ MongoDB จะอัพเดทเฉพาะฟิลด์ใหม่ (โดยไม่ต้องระบุทีละหนึ่งอย่างชัดเจน) เพื่อรับสิ่งนี้:

{
   _id : ...,
   some_key: { 
        param1 : "val1",
        param2 : "val2_new",
        param3 : "val3_new"
   }
}

ฉันใช้ไคลเอนต์ Java แต่ตัวอย่างใดจะได้รับการชื่นชม


2
โปรดลงคะแนนสำหรับปัญหา $ merge: jira.mongodb.org/browse/SERVER-21094
Ali

คำตอบ:


107

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

ไม่มีวิธีการทำเช่นนั้นในคำสั่งเดียว

คุณต้องค้นหาเอกสารก่อนแล้วหาสิ่งที่คุณต้องการ$setแล้วอัปเดต (ใช้ค่าเก่าเป็นตัวกรองที่ตรงกันเพื่อให้แน่ใจว่าคุณจะไม่ได้รับการอัพเดทพร้อมกันในระหว่างนั้น)


การอ่านคำถามของคุณอีกอย่างก็คือคุณมีความสุข$setแต่ไม่ต้องการตั้งค่าฟิลด์ทั้งหมดอย่างชัดเจน คุณจะส่งผ่านข้อมูลอย่างไร

คุณรู้ว่าคุณสามารถทำสิ่งต่อไปนี้:

db.collection.update(  { _id:...} , { $set: someObjectWithNewData } 

หากคุณต้องการตั้งค่าทุกฟิลด์โดยไม่มีเงื่อนไขคุณสามารถใช้พารามิเตอร์$ multiเช่นนี้: db.collection.update( { _id:...} , { $set: someObjectWithNewData, { $multi: true } } )
Villager

12
ไม่มีพารามิเตอร์หลายค่า มีตัวเลือกมากมาย (ซึ่งเป็นสิ่งที่คุณเชื่อมโยง) แต่ไม่มีผลต่อวิธีการอัปเดตฟิลด์ มันควบคุมว่าคุณจะปรับปรุงเอกสารเดียวหรือเอกสารทั้งหมดที่ตรงกับพารามิเตอร์แบบสอบถาม
Bob Kerns

1
มันไม่สมเหตุสมผลอะไรเลย เมื่อจัดการกับ mongo เป็นที่ต้องการสำหรับการอัพเดตที่จะมีการทำงานของอะตอม การอ่านครั้งแรกจะทำให้การอ่านสกปรกเมื่อมีคนอื่นเปลี่ยนแปลงข้อมูลของคุณ และเนื่องจาก mongo ไม่มีการทำธุรกรรมมันจะทำให้ข้อมูลของคุณเสียหายอย่างมีความสุข
froginvasion

@froginvasion: คุณสามารถทำให้เป็นอะตอมมิกโดยใช้ค่าเก่าเป็นตัวกรองที่ตรงกันในการอัปเดตวิธีนี้การอัปเดตของคุณจะล้มเหลวหากมีการอัปเดตพร้อมกันที่ขัดแย้งกันในระหว่างนี้ จากนั้นคุณสามารถตรวจจับสิ่งนั้นและดำเนินการตามนั้น สิ่งนี้เรียกว่าการล็อคในแง่ดี แต่ใช่ขึ้นอยู่กับแอปพลิเคชันของคุณที่ใช้อย่างถูกต้อง Mongo เองไม่มีธุรกรรมในตัว
Thilo

ฉันคิดว่านี่เป็นคำถามทั่วไปของการรวมสองวัตถุใน javascript IIRC แนวคิดคือการกำหนดคุณสมบัติของใหม่ให้แก่เก่า
information_interchange

110

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

ตัวอย่าง:

{
    _id : ...,
    some_key: { 
        param1 : "val1",
        param2 : "val2",
        param3 : "val3"
    }
}

หากคุณต้องการอัปเดต param2 เท่านั้นมันผิดที่ต้องทำ:

db.collection.update(  { _id:...} , { $set: { some_key : new_info  } }  //WRONG

คุณต้องใช้:

db.collection.update(  { _id:...} , { $set: { some_key.param2 : new_info  } } 

ดังนั้นฉันจึงเขียนฟังก์ชั่นดังนี้:

function _update($id, $data, $options=array()){

    $temp = array();
    foreach($data as $key => $value)
    {
        $temp["some_key.".$key] = $value;
    } 

    $collection->update(
        array('_id' => $id),
        array('$set' => $temp)
    );

}

_update('1', array('param2' => 'some data'));

11
นี่ควรเป็นคำตอบที่ยอมรับได้ สำหรับฟังก์ชั่น flattenObject ที่ดีขึ้นโปรดดู gist.github.com/penguinboy/762197
steph643

ขอบคุณคำตอบของคุณช่วยแนะนำฉัน ฉันมีความสามารถในการปรับเปลี่ยนค่าของคีย์เฉพาะของผู้ใช้ในคอลเลกชันผู้ใช้ดังนี้:db.users.update( { _id: ObjectId("594dbc3186bb5c84442949b1") }, { $set: { resume: { url: "http://i.imgur.com/UFX0yXs.jpg" } } } )
ลุค Schoen

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

21

คุณสามารถใช้เครื่องหมายจุดเพื่อเข้าถึงและตั้งค่าฟิลด์ในส่วนลึกของวัตถุโดยไม่ส่งผลกระทบต่อคุณสมบัติอื่นของวัตถุเหล่านั้น

รับวัตถุที่คุณระบุด้านบน:

> db.test.insert({"id": "test_object", "some_key": {"param1": "val1", "param2": "val2", "param3": "val3"}})
WriteResult({ "nInserted" : 1 })

เราสามารถอัปเดตเพียงsome_key.param2และsome_key.param3:

> db.test.findAndModify({
... query: {"id": "test_object"},
... update: {"$set": {"some_key.param2": "val2_new", "some_key.param3": "val3_new"}},
... new: true
... })
{
    "_id" : ObjectId("56476e04e5f19d86ece5b81d"),
    "id" : "test_object",
    "some_key" : {
        "param1" : "val1",
        "param2" : "val2_new",
        "param3" : "val3_new"
    }
}

คุณสามารถเจาะลึกเท่าที่คุณต้องการ สิ่งนี้ยังมีประโยชน์สำหรับการเพิ่มคุณสมบัติใหม่ให้กับวัตถุ


ฉันสามารถเพิ่มรหัสใหม่ ... เช่น "บางคีย์": {"param1": "val1", "param2": "val2_new", "param3": "val3_new", "param4": "val4_new",}
Urvashi Soni

17

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

https://www.npmjs.com/package/mongo-dot-notation

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

นำมาจากmongo-dot-notationเอกสาร:

var person = {
  firstName: 'John',
  lastName: 'Doe',
  address: {
    city: 'NY',
    street: 'Eighth Avenu',
    number: 123
  }
};



var instructions = dot.flatten(person)
console.log(instructions);
/* 
{
  $set: {
    'firstName': 'John',
    'lastName': 'Doe',
    'address.city': 'NY',
    'address.street': 'Eighth Avenu',
    'address.number': 123
  }
}
*/

และจากนั้นก็เป็นตัวเลือกที่สมบูรณ์แบบ - มันจะอัปเดตคุณสมบัติที่กำหนดเท่านั้น แก้ไข: ฉันชอบเป็นนักโบราณคดีบางครั้ง;)


10

Mongo ให้คุณอัพเดทเอกสารที่ซ้อนกันโดยใช้หลักการ.ประชุม ลองดู: การอัปเดตเอกสารที่ซ้อนกันใน MongoDB ต่อไปนี้เป็นคำถามจากอดีตเกี่ยวกับการอัปเดตการผสานเช่นเดียวกับที่คุณกำลังมองหาฉันเชื่อว่า: MongoDB อัปเดตอะตอมมิกผ่านเอกสาร 'ผสาน'


6

ฉันประสบความสำเร็จในการทำเช่นนี้:

db.collection.update(  { _id:...} , { $set: { 'key.another_key' : new_info  } } );

ฉันมีฟังก์ชั่นที่จัดการอัพเดตโปรไฟล์ของฉันแบบไดนามิก

function update(prop, newVal) {
  const str = `profile.${prop}`;
  db.collection.update( { _id:...}, { $set: { [str]: newVal } } );
}

หมายเหตุ: 'โปรไฟล์' เป็นข้อมูลเฉพาะสำหรับการนำไปใช้ของฉันเป็นเพียงสตริงของคีย์ที่คุณต้องการแก้ไข


1

ดูเหมือนว่าคุณสามารถตั้งค่าisPartialObjectซึ่งอาจบรรลุสิ่งที่คุณต้องการ


ลองมัน มัน doens't ช่วยให้คุณบันทึกวัตถุบางส่วนถ้าฉันไม่ผิด ... แต่ขอบคุณ
Eran เมดาน

1
    // where clause DBObject
    DBObject query = new BasicDBObject("_id", new ObjectId(id));

    // modifications to be applied
    DBObject update = new BasicDBObject();

    // set new values
    update.put("$set", new BasicDBObject("param2","value2"));

   // update the document
    collection.update(query, update, true, false); //3rd param->upsertFlag, 4th param->updateMultiFlag

หากคุณมีหลายช่องที่ต้องอัพเดท

        Document doc = new Document();
        doc.put("param2","value2");
        doc.put("param3","value3");
        update.put("$set", doc);

1

เริ่มต้นMongo 4.2, db.collection.update()สามารถยอมรับท่อรวมซึ่งจะช่วยให้ผู้ประกอบการใช้รวมเช่น$addFieldsที่เอาท์พุทฟิลด์ที่มีอยู่ทั้งหมดจากเอกสารการป้อนข้อมูลและสาขาที่เพิ่มใหม่:

var new_info = { param2: "val2_new", param3: "val3_new" }

// { some_key: { param1: "val1", param2: "val2", param3: "val3" } }
// { some_key: { param1: "val1", param2: "val2"                 } }
db.collection.update({}, [{ $addFields: { some_key: new_info } }], { multi: true })
// { some_key: { param1: "val1", param2: "val2_new", param3: "val3_new" } }
// { some_key: { param1: "val1", param2: "val2_new", param3: "val3_new" } }
  • ส่วนแรก{}คือข้อความค้นหาที่ตรงกันกรองเอกสารที่จะอัปเดต (ในกรณีนี้คือเอกสารทั้งหมด)

  • ส่วนที่สอง[{ $addFields: { some_key: new_info } }]คือขั้นตอนการรวมการอัพเดท:

    • สังเกตว่าวงเล็บเหลี่ยมกำลังสองที่แสดงถึงการใช้ไพพ์ไลน์การรวม
    • $addFieldsตั้งแต่นี้เป็นท่อรวมเราสามารถใช้
    • $addFields ดำเนินการตามที่คุณต้องการ: อัปเดตวัตถุเพื่อให้วัตถุใหม่ซ้อนทับ / ผสานกับวัตถุที่มีอยู่:
    • ในกรณีนี้{ param2: "val2_new", param3: "val3_new" }จะถูกรวมเข้าไปในที่มีอยู่some_keyโดยการเก็บรักษาparam1ไม่ถูกแตะต้องและทั้งเพิ่มหรือแทนที่ทั้งสองและparam2param3
  • อย่าลืม{ multi: true }มิฉะนั้นเฉพาะเอกสารการจับคู่แรกเท่านั้นที่จะได้รับการอัปเดต


1
db.collection.update(  { _id:...} , { $set: { some_key : new_info  } } 

ถึง

db.collection.update( { _id: ..} , { $set: { some_key: { param1: newValue} } } ); 

หวังว่าจะช่วยได้!


1

คุณค่อนข้างจะโกรธมากการดำเนินการนี้ใน MongoDB ใช้เพื่อบันทึกเอกสารลงในคอลเลกชัน หากเอกสารตรงกับเกณฑ์แบบสอบถามจะดำเนินการอัปเดตมิฉะนั้นจะแทรกเอกสารใหม่ลงในคอลเลกชัน

สิ่งที่คล้ายกันดังนี้

db.employees.update(
    {type:"FT"},
    {$set:{salary:200000}},
    {upsert : true}
 )

0

ใช้ $ set ทำกระบวนการนี้

.update({"_id": args.dashboardId, "viewData._id": widgetId}, {$set: {"viewData.$.widgetData": widgetDoc.widgetData}})
.exec()
.then(dashboardDoc => {
    return {
        result: dashboardDoc
    };
}); 

0

สร้างอ็อบเจ็กต์การอัพเดตที่มีชื่อคุณสมบัติรวมถึงจุดพา ธ ที่จำเป็น ("somekey." + จากตัวอย่างของ OP) จากนั้นใช้การอัปเดตนั้น

//the modification that's requested
updateReq = { 
   param2 : "val2_new",
   param3 : "val3_new"   
}

//build a renamed version of the update request
var update = {};
for(var field in updateReq){
    update["somekey."+field] = updateReq[field];
}

//apply the update without modifying fields not originally in the update
db.collection.update({._id:...},{$set:update},{upsert:true},function(err,result){...});

0

ใช่วิธีที่ดีที่สุดคือการแปลงสัญกรณ์วัตถุเป็นการแสดงสตริงคีย์ - ค่าแบบแบนตามที่ระบุไว้ในความคิดเห็นนี้: https://stackoverflow.com/a/39357531/2529199

ฉันต้องการเน้นวิธีทางเลือกโดยใช้ไลบรารี NPM นี้: https://www.npmjs.com/package/dot-objectซึ่งช่วยให้คุณสามารถจัดการวัตถุต่าง ๆ โดยใช้เครื่องหมายจุด

ฉันใช้รูปแบบนี้เพื่อสร้างคุณสมบัติอ็อบเจ็กต์ที่ซ้อนกันโดยทางโปรแกรมเมื่อยอมรับคีย์ - ค่าเป็นตัวแปรฟังก์ชันดังนี้:

const dot = require('dot-object');

function(docid, varname, varvalue){
  let doc = dot.dot({
      [varname]: varvalue 
  });

  Mongo.update({_id:docid},{$set:doc});
}

รูปแบบนี้ให้ฉันใช้คุณสมบัติซ้อนกันเช่นเดียวกับคุณสมบัติระดับเดียวสลับกันได้และแทรกลงใน Mongo อย่างหมดจด

หากคุณต้องการเล่นกับ JS Objects นอกเหนือจาก Mongo โดยเฉพาะในฝั่งไคลเอ็นต์ แต่มีความสอดคล้องเมื่อทำงานกับ Mongo ไลบรารีนี้จะให้ตัวเลือกแก่คุณมากกว่าmongo-dot-notationโมดูล NPM ที่กล่าวถึงก่อนหน้า

ป.ล. ฉันต้องการพูดถึงสิ่งนี้เป็นความคิดเห็น แต่เห็นได้ชัดว่าตัวแทน S / O ของฉันไม่สูงพอที่จะโพสต์ความคิดเห็น ดังนั้นไม่พยายามที่จะแสดงความคิดเห็นของ SzybkiSasza เพียงแค่ต้องการเน้นการจัดหาโมดูลทางเลือก



0

คุณควรคิดเกี่ยวกับการปรับปรุงวัตถุที่สามารถสับเปลี่ยนกันได้และจากนั้นเพียงแค่เก็บวัตถุด้วยเขตข้อมูลที่ปรับปรุงแล้ว สิ่งที่ชอบทำด้านล่าง

function update(_id) {
    return new Promise((resolve, reject) => {

        ObjModel.findOne({_id}).exec((err, obj) => {

            if(err) return reject(err)

            obj = updateObject(obj, { 
                some_key: {
                    param2 : "val2_new",
                    param3 : "val3_new"
                }
            })

            obj.save((err, obj) => {
                if(err) return reject(err)
                resolve(obj)
            })
        })
    })
}


function updateObject(obj, data) {

    let keys = Object.keys(data)

    keys.forEach(key => {

        if(!obj[key]) obj[key] = data[key]

        if(typeof data[key] == 'object') 
            obj[key] = updateObject(obj[key], data[key])
        else
            obj[key] = data[key]
    })

    return obj
}

0

คุณต้องใช้เอกสารที่ฝังตัว (stringfy วัตถุเส้นทาง)

let update = {}
Object.getOwnPropertyNames(new_info).forEach(param => {
   update['some_key.' + param] = new_info[param]
})

ดังนั้นใน JavaScript คุณสามารถใช้ Spread Operator (... ) เพื่ออัปเดต

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