Jak częściowo zaktualizować obiekt w MongoDB, aby nowy obiekt nałożył się / scalił z istniejącym

183

Biorąc pod uwagę ten dokument zapisany w MongoDB

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

Obiekt z nowymi informacjami pochodzącymi ze świata zewnętrznego param2i param3ze świata zewnętrznego musi zostać uratowany

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

Chcę scalić / nałożyć nowe pola na istniejący stan obiektu, aby parametr param1 nie został usunięty

Robiąc to

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

Prowadzi do MongoDB robi dokładnie to, o co go proszono, i ustawia some_key na tę wartość. wymiana starego.

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

Jaki jest sposób, aby MongoDB aktualizował tylko nowe pola (bez ich jawnego podawania jeden po drugim)? aby uzyskać to:

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

Używam klienta Java, ale każdy przykład będzie mile widziany

Eran Medan
źródło
2
proszę głosować w sprawie $ merge: jira.mongodb.org/browse/SERVER-21094
Ali

Odpowiedzi:

107

Jeśli dobrze rozumiem pytanie, chcesz zaktualizować dokument o zawartość innego dokumentu, ale tylko te pola, których już nie ma, i całkowicie zignorować pola, które są już ustawione (nawet jeśli na inną wartość).

Nie da się tego zrobić jednym poleceniem.

Musisz najpierw $setwysłać zapytanie do dokumentu, dowiedzieć się, co chcesz, a następnie zaktualizować go (używając starych wartości jako pasującego filtru, aby upewnić się, że nie otrzymasz między nimi równoczesnych aktualizacji).


Innym sposobem odczytania Twojego pytania byłoby to, że jesteś zadowolony $set, ale nie chcesz jawnie ustawiać wszystkich pól. Jak wtedy przekazałbyś dane?

Wiesz, że możesz wykonać następujące czynności:

db.collection.update(  { _id:...} , { $set: someObjectWithNewData } 
Thilo
źródło
Jeśli chcesz ustawić wszystkie pola bezwarunkowo, możesz użyć parametru $ multi , na przykład: db.collection.update( { _id:...} , { $set: someObjectWithNewData, { $multi: true } } )
Wieśniak
12
Nie ma parametru $ multi. Dostępnych jest wiele opcji (z którymi się łączysz), ale nie ma to wpływu na aktualizację pól. Określa, czy aktualizujesz pojedynczy dokument, czy wszystkie dokumenty, które pasują do parametru zapytania.
Bob Kerns
1
To nie ma żadnego sensu. W przypadku mongo preferowane jest również, aby aktualizacje miały operacje atomowe. Pierwsze czytanie powoduje brudny odczyt, gdy ktoś inny zmieni twoje dane. A ponieważ mongo nie ma transakcji, na szczęście uszkodzi dane.
froginvasion
@froginvasion: Możesz uczynić ją atomową, używając starych wartości jako pasującego filtru podczas aktualizacji. Możesz to wykryć i odpowiednio zareagować. Nazywa się to optymistycznym blokowaniem. Ale tak, zależy to od tego, czy aplikacja poprawnie ją zaimplementuje, samo Mongo nie ma wbudowanych transakcji.
Thilo
Myślę, że jest to bardziej ogólna kwestia łączenia dwóch obiektów w javascript. IIRC chodzi o to, aby po prostu przypisać właściwości nowego do starego
information_interchange
110

Rozwiązałem to własną funkcją. Jeśli chcesz zaktualizować określone pole w dokumencie, musisz to wyraźnie rozwiązać.

Przykład:

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

Jeśli chcesz zaktualizować tylko param2, to źle:

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

Musisz użyć:

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

Napisałem więc taką funkcję:

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'));
mehmatrix
źródło
11
To powinna być akceptowana odpowiedź. Aby uzyskać lepszą funkcję flattenObject, zobacz: gist.github.com/penguinboy/762197
steph643
Dzięki, twoja odpowiedź pomogła mi poprowadzić. Mogłem zmodyfikować wartość określonego klucza użytkownika w kolekcji użytkowników w następujący sposób:db.users.update( { _id: ObjectId("594dbc3186bb5c84442949b1") }, { $set: { resume: { url: "http://i.imgur.com/UFX0yXs.jpg" } } } )
Luke Schoen,
Czy ta operacja jest atomowa i izolowana? Oznacza to, że jeśli 2 równoległe wątki spróbują ustawić 2 różne pola tego samego dokumentu, czy spowoduje to stan wyścigu i nieprzewidywalny wynik
so-random-dude
21

Możesz użyć notacji kropkowej, aby uzyskać dostęp do pól w głębi obiektów i ustawić je bez wpływu na inne właściwości tych obiektów.

Biorąc pod uwagę przedmiot, który podałeś powyżej:

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

Aktualizujemy tylko some_key.param2i 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"
    }
}

Możesz zagłębiać się tak głęboko, jak chcesz. Jest to również przydatne przy dodawaniu nowych właściwości do obiektu bez wpływu na już istniejące.

Samir Talwar
źródło
Czy mogę dodać nowy klucz ... np. „Jakiś klucz”: {"param1": "val1", "param2": "val2_new", "param3": "val3_new", "param4": "val4_new",}
Urvashi Soni
17

Najlepszym rozwiązaniem jest wyodrębnienie właściwości z obiektu i utworzenie z nich płaskich par klucz-wartość w notacji kropkowej. Możesz na przykład użyć tej biblioteki:

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

Posiada .flattenfunkcję, która pozwala na zmianę obiektu na płaski zestaw właściwości, które można następnie przekazać modyfikatorowi $ set, bez obaw, że jakakolwiek właściwość istniejącego obiektu DB zostanie usunięta / nadpisana bez potrzeby.

Zaczerpnięte z mongo-dot-notationdokumentów:

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

A potem tworzy doskonały selektor - zaktualizuje TYLKO podane właściwości. EDYCJA: Czasami lubię być archeologiem;)

SzybkiSasza
źródło
6

Udało mi się to zrobić w ten sposób:

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

Mam funkcję, która dynamicznie obsługuje aktualizacje mojego profilu

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

Uwaga: „profil” jest specyficzny dla mojej implementacji, jest to tylko ciąg klucza, który chcesz zmodyfikować.

Matt Smith
źródło
1

Wygląda na to, że możesz ustawić isPartialObject, który może osiągnąć to, co chcesz.

Patrick Tescher
źródło
spróbował tego. nie pozwala ci zapisywać częściowych obiektów, jeśli się nie mylę ... ale dzięki
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

Jeśli masz wiele pól do zaktualizowania

        Document doc = new Document();
        doc.put("param2","value2");
        doc.put("param3","value3");
        update.put("$set", doc);
Vino
źródło
1

Zaczynając Mongo 4.2, db.collection.update()może zaakceptować potok agregacji, który pozwala na użycie operatorów agregacji, takich jak $addFields, który wyprowadza wszystkie istniejące pola z dokumentów wejściowych i nowo dodanych pól:

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" } }
  • Pierwsza część {}to kwerenda dopasowująca, filtrująca dokumenty do zaktualizowania (w tym przypadku wszystkie dokumenty).

  • Druga część [{ $addFields: { some_key: new_info } }]to potok agregacji aktualizacji:

    • Zwróć uwagę na kwadratowe nawiasy wskazujące na użycie potoku agregacji.
    • Ponieważ jest to potok agregacji, możemy użyć $addFields.
    • $addFields wykonuje dokładnie to, czego potrzebujesz: aktualizuje obiekt, aby nowy obiekt nałożył się / scalił z istniejącym:
    • W takim przypadku { param2: "val2_new", param3: "val3_new" }zostanie scalony z istniejącym some_key, pozostawiając param1nietknięty i dodając lub zastępując oba param2i param3.
  • Nie zapomnij { multi: true }, w przeciwnym razie zaktualizowany zostanie tylko pierwszy pasujący dokument.

Xavier Guihot
źródło
1
db.collection.update(  { _id:...} , { $set: { some_key : new_info  } } 

do

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

Mam nadzieję, że to pomoże!

nguyenthang338
źródło
1

Możesz raczej zrobić upsert, ta operacja w MongoDB jest wykorzystywana do zapisywania dokumentu w kolekcji. Jeśli dokument spełnia kryteria zapytania, wykona operację aktualizacji, w przeciwnym razie wstawi nowy dokument do kolekcji.

coś podobnego jak poniżej

db.employees.update(
    {type:"FT"},
    {$set:{salary:200000}},
    {upsert : true}
 )
Pravin
źródło
0

użyj $ set wykonaj ten proces

.update({"_id": args.dashboardId, "viewData._id": widgetId}, {$set: {"viewData.$.widgetData": widgetDoc.widgetData}})
.exec()
.then(dashboardDoc => {
    return {
        result: dashboardDoc
    };
}); 
KARTHIKEYAN.A
źródło
0

Utwórz obiekt aktualizacji z nazwami właściwości, w tym wymaganą ścieżką kropki. („jakiś klucz.” + z przykładu OP), a następnie użyj go do aktualizacji.

//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){...});
Andrzej
źródło
0

Tak, najlepszym sposobem jest przekonwertowanie notacji obiektu na płaską reprezentację ciągu klucz-wartość, jak wspomniano w tym komentarzu: https://stackoverflow.com/a/39357531/2529199

Chciałem podkreślić alternatywną metodę wykorzystującą tę bibliotekę NPM: https://www.npmjs.com/package/dot-object, która umożliwia manipulowanie różnymi obiektami za pomocą notacji kropkowej.

Użyłem tego wzorca, aby programowo utworzyć zagnieżdżoną właściwość obiektu, akceptując klucz-wartość jako zmienną funkcji, w następujący sposób:

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

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

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

Ten wzorzec pozwala mi zamiennie używać zarówno właściwości zagnieżdżonych, jak i właściwości jednopoziomowych i wstawiać je czysto do Mongo.

Jeśli potrzebujesz bawić się obiektami JS poza tylko Mongo, szczególnie po stronie klienta, ale zachowujesz spójność podczas pracy z Mongo, ta biblioteka zapewnia więcej opcji niż wspomniany wcześniej mongo-dot-notationmoduł NPM.

PS Początkowo chciałem tylko wspomnieć o tym jako komentarz, ale najwyraźniej mój przedstawiciel S / O nie jest wystarczająco wysoki, aby opublikować komentarz. Tak więc, nie próbując wtrącać się w komentarz Szybkiego Saszzy, chciałem tylko podkreślić, że zapewniam alternatywny moduł.

Ashwin Shankar
źródło
0

Powinieneś pomyśleć o aktualizowaniu obiektu zamiennie, a następnie po prostu przechowywać obiekt ze zaktualizowanymi polami. Coś jak zrobiono poniżej

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
}
Ivan Bravo Mendoza
źródło
0

Musisz użyć osadzonych dokumentów (określ obiekt ścieżki)

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

I tak, w JavaScript możesz użyć operatora Spread (...) do aktualizacji

db.collection.update(  { _id:...} , { $set: { ...update  } } 
Guihgo
źródło