Làm cách nào để cập nhật một phần đối tượng trong MongoDB để đối tượng mới sẽ chồng / hợp nhất với đối tượng hiện có


183

Đưa ra tài liệu này được lưu trong MongoDB

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

Một đối tượng có thông tin mới trên param2param3từ thế giới bên ngoài cần được lưu lại

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

Tôi muốn hợp nhất / lớp phủ các trường mới trên trạng thái hiện có của đối tượng để param1 không bị xóa

Làm cái này

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

Sẽ dẫn đến MongoDB đang làm đúng như yêu cầu và đặt some_key thành giá trị đó. thay cái cũ.

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

Cách để MongoDB chỉ cập nhật các trường mới (mà không nêu rõ từng trường một)? để có được điều này:

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

Tôi đang sử dụng máy khách Java, nhưng bất kỳ ví dụ nào cũng sẽ được đánh giá cao


2
vui lòng bỏ phiếu cho vấn đề hợp nhất $: jira.mongodb.org/browse/SERVER-21094
Ali

Câu trả lời:


107

Nếu tôi hiểu chính xác câu hỏi, bạn muốn cập nhật tài liệu với nội dung của tài liệu khác, nhưng chỉ các trường chưa có và hoàn toàn bỏ qua các trường đã được đặt (ngay cả khi có giá trị khác).

Không có cách nào để làm điều đó trong một lệnh duy nhất.

Bạn phải truy vấn tài liệu trước, tìm ra những gì bạn muốn $setvà sau đó cập nhật nó (sử dụng các giá trị cũ làm bộ lọc phù hợp để đảm bảo bạn không nhận được các cập nhật đồng thời ở giữa).


Một cách đọc câu hỏi khác của bạn sẽ là bạn hài lòng $set, nhưng không muốn đặt rõ ràng tất cả các trường. Làm thế nào bạn sẽ vượt qua trong dữ liệu sau đó?

Bạn biết bạn có thể làm như sau:

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

Nếu bạn muốn đặt tất cả các trường vô điều kiện, bạn có thể sử dụng tham số $ multi , như sau: db.collection.update( { _id:...} , { $set: someObjectWithNewData, { $multi: true } } )
Dân làng

12
Không có tham số $ multi. Có nhiều tùy chọn (đó là những gì bạn đã liên kết đến), nhưng điều đó không ảnh hưởng đến cách các trường được cập nhật. Nó kiểm soát xem bạn cập nhật một tài liệu hay tất cả các tài liệu phù hợp với tham số truy vấn.
Bob Kerns

1
Điều này không có ý nghĩa gì. Khi giao dịch với mongo, nó cũng được ưu tiên cho các bản cập nhật để có các hoạt động nguyên tử. Lần đầu tiên đọc nó gây ra đọc bẩn bất cứ khi nào người khác thay đổi dữ liệu của bạn. Và vì mongo không có giao dịch, nó sẽ vui vẻ làm hỏng dữ liệu cho bạn.
froginvasion

@froginvasion: Bạn có thể biến nó thành nguyên tử bằng cách sử dụng các giá trị cũ làm bộ lọc phù hợp trên bản cập nhật. Cách đó, bản cập nhật của bạn sẽ thất bại nếu có một bản cập nhật đồng thời mâu thuẫn. Sau đó, bạn có thể phát hiện điều đó và hành động phù hợp. Điều này được gọi là khóa lạc quan. Nhưng vâng, nó phụ thuộc vào ứng dụng của bạn triển khai đúng cách, bản thân Mongo không có giao dịch tích hợp.
Thilo

Tôi nghĩ rằng đây là một câu hỏi chung hơn về việc hợp nhất hai đối tượng trong javascript. Ý tưởng của IIRC chỉ đơn giản là gán các thuộc tính của cái mới cho cái cũ
information_interchange

110

Tôi đã giải quyết nó với chức năng của riêng tôi. Nếu bạn muốn cập nhật trường được chỉ định trong tài liệu, bạn cần giải quyết rõ ràng.

Thí dụ:

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

Nếu bạn chỉ muốn cập nhật param2, thì sai:

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

Bạn phải dùng:

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

Vì vậy, tôi đã viết một chức năng như thế:

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
Đây phải là câu trả lời được chấp nhận. Để có chức năng flattenObject tốt hơn, hãy xem gist.github.com/penguinboy/762197
steph643

Cảm ơn phản hồi của bạn đã giúp hướng dẫn tôi. Tôi đã chấp nhận sửa đổi giá trị của một khóa cụ thể của người dùng trong bộ sưu tập người dùng như sau:db.users.update( { _id: ObjectId("594dbc3186bb5c84442949b1") }, { $set: { resume: { url: "http://i.imgur.com/UFX0yXs.jpg" } } } )
Luke Schoen

Là hoạt động này nguyên tử và cô lập? Có nghĩa là, nếu 2 luồng song song cố gắng đặt 2 trường khác nhau của cùng một tài liệu, điều đó sẽ dẫn đến tình trạng chủng tộc và kết quả không thể đoán trước
rất ngẫu nhiên-anh chàng

21

Bạn có thể sử dụng ký hiệu chấm để truy cập và đặt các trường nằm sâu bên trong các đối tượng, mà không ảnh hưởng đến các thuộc tính khác của các đối tượng đó.

Đưa ra đối tượng bạn đã chỉ định ở trên:

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

Chúng tôi có thể cập nhật chỉ some_key.param2some_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"
    }
}

Bạn có thể đào sâu như bạn muốn. Điều này cũng hữu ích để thêm các thuộc tính mới vào một đối tượng mà không ảnh hưởng đến các thuộc tính hiện có.


Tôi có thể thêm khóa mới không ... như "một số khóa": {"param1": "val1", "param2": "val2_new", "param3": "val3_new", "param4": "val4_new",}
Urvashi Soni

17

Giải pháp tốt nhất là trích xuất các thuộc tính từ đối tượng và biến chúng thành các cặp khóa-giá trị dấu chấm phẳng. Bạn có thể sử dụng ví dụ thư viện này:

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

Nó có .flatten chức năng cho phép bạn thay đổi đối tượng thành tập các thuộc tính phẳng có thể được trao cho công cụ sửa đổi $ set, mà không phải lo lắng rằng bất kỳ thuộc tính nào của đối tượng DB hiện tại của bạn sẽ bị xóa / ghi đè mà không cần.

Được lấy từ mongo-dot-notation tài liệu:

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
  }
}
*/

Và sau đó, nó tạo thành bộ chọn hoàn hảo - nó sẽ cập nhật CHỈ các thuộc tính đã cho. EDIT: Tôi thích trở thành nhà khảo cổ học một số lần;)



6

Tôi đã thành công khi làm theo cách này:

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

Tôi có một chức năng xử lý cập nhật hồ sơ của tôi một cách linh hoạt

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

Lưu ý: 'hồ sơ' là dành riêng cho việc triển khai của tôi, nó chỉ là chuỗi khóa mà bạn muốn sửa đổi.


1

Có vẻ như bạn có thể đặt isPartialObject có thể thực hiện những gì bạn muốn.


thử nó. nó không cho phép bạn lưu một phần các đối tượng nếu tôi không nhầm ... nhưng cảm ơn
Eran Medan

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

Nếu bạn có nhiều lĩnh vực được cập nhật

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

1

Bắt đầu Mongo 4.2, db.collection.update()có thể chấp nhận một đường ống tổng hợp, cho phép sử dụng các toán tử tổng hợp như $addFields, đầu ra tất cả các trường hiện có từ các tài liệu đầu vào và các trường mới được thêm vào:

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" } }
  • Phần đầu tiên {}là truy vấn khớp, lọc những tài liệu cần cập nhật (trong trường hợp này là tất cả các tài liệu).

  • Phần thứ hai [{ $addFields: { some_key: new_info } }]là đường ống tổng hợp cập nhật:

    • Lưu ý dấu ngoặc vuông biểu thị việc sử dụng đường ống tổng hợp.
    • Vì đây là một đường ống tổng hợp, chúng tôi có thể sử dụng $addFields.
    • $addFields thực hiện chính xác những gì bạn cần: cập nhật đối tượng để đối tượng mới sẽ chồng / hợp nhất với đối tượng hiện có:
    • Trong trường hợp này, { param2: "val2_new", param3: "val3_new" }sẽ được hợp nhất vào hiện tại some_keybằng cách giữ param1nguyên và không thêm hoặc thay thế cả param2param3.
  • Đừng quên { multi: true }, nếu không thì chỉ có tài liệu phù hợp đầu tiên sẽ được cập nhật.


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

đến

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

Hy vọng điều này giúp đỡ!


1

Bạn có thể thực hiện một thao tác nâng cao, thao tác này trong MongoDB được sử dụng để lưu tài liệu vào bộ sưu tập. Nếu tài liệu phù hợp với tiêu chí truy vấn thì nó sẽ thực hiện thao tác cập nhật nếu không nó sẽ chèn một tài liệu mới vào bộ sưu tập.

một cái gì đó tương tự như dưới đây

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

0

sử dụng $ set để thực hiện quá trình này

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

0

Tạo một đối tượng cập nhật với các tên thuộc tính bao gồm đường dẫn dấu chấm cần thiết. ("somekey." + từ ví dụ của OP), và sau đó sử dụng bản cập nhật đó.

//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

Vâng, cách tốt nhất là chuyển đổi ký hiệu đối tượng thành biểu diễn chuỗi khóa-giá trị phẳng, như đã đề cập trong nhận xét này: https://stackoverflow.com/a/39357531/2529199

Tôi muốn làm nổi bật một phương pháp thay thế bằng thư viện NPM này: https://www.npmjs.com/package/dot-object cho phép bạn thao tác các đối tượng khác nhau bằng cách sử dụng ký hiệu dấu chấm.

Tôi đã sử dụng mẫu này để lập trình một thuộc tính đối tượng lồng nhau khi chấp nhận khóa-giá trị như một biến hàm, như sau:

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

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

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

Mẫu này cho phép tôi sử dụng các thuộc tính cấp độ lồng nhau cũng như đơn lẻ và có thể thay thế chúng một cách sạch sẽ vào Mongo.

Nếu bạn cần chơi xung quanh với các Đối tượng JS ngoài Mongo, đặc biệt là về phía máy khách nhưng có tính nhất quán khi làm việc với Mongo, thư viện này cung cấp cho bạn nhiều tùy chọn hơn mongo-dot-notationmô-đun NPM đã đề cập trước đó .

PS ban đầu tôi chỉ muốn đề cập đến điều này như một bình luận nhưng rõ ràng đại diện S / O của tôi không đủ cao để đăng bình luận. Vì vậy, không cố gắng tập trung vào bình luận của SzybkiSasza, chỉ muốn làm nổi bật việc cung cấp một mô-đun thay thế.



0

Bạn nên suy nghĩ về việc cập nhật đối tượng thay thế cho nhau và sau đó chỉ cần lưu trữ đối tượng với các trường được cập nhật. Một cái gì đó giống như được thực hiện dưới đây

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

Bạn phải sử dụng Tài liệu nhúng (chuỗi đối tượng đường dẫn)

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

Và do đó, trong JavaScript, bạn có thể sử dụng Toán tử trải rộng (...) để cập nhật

db.collection.update(  { _id:...} , { $set: { ...update  } } 
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.