Come posso aggiornare parzialmente un oggetto in MongoDB in modo che il nuovo oggetto si sovrapponga / si unisca a quello esistente


183

Dato questo documento salvato in MongoDB

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

Un oggetto con nuove informazioni da param2e param3verso il mondo esterno deve essere salvato

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

Voglio unire / sovrapporre i nuovi campi allo stato esistente dell'oggetto in modo che param1 non venga rimosso

Facendo questo

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

Porterà a MongoDB sta facendo esattamente come è stato chiesto e imposta some_key su quel valore. sostituendo quello vecchio.

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

Qual è il modo in cui MongoDB aggiorna solo i nuovi campi (senza indicarli esplicitamente uno per uno)? per ottenere questo:

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

Sto usando il client Java, ma qualsiasi esempio sarà apprezzato


2
si prega di votare per il problema di $ merge: jira.mongodb.org/browse/SERVER-21094
Ali

Risposte:


107

Se capisco correttamente la domanda, vuoi aggiornare un documento con i contenuti di un altro documento, ma solo i campi che non sono già presenti e ignorare completamente i campi che sono già impostati (anche se a un altro valore).

Non c'è modo di farlo in un solo comando.

Devi prima interrogare il documento, capire cosa vuoi $sete quindi aggiornarlo (usando i vecchi valori come filtro di corrispondenza per assicurarti di non ricevere aggiornamenti simultanei nel mezzo).


Un'altra lettura della tua domanda potrebbe essere la tua soddisfazione $set, ma non vuoi impostare esplicitamente tutti i campi. Come trasferiresti i dati allora?

Sai che puoi fare quanto segue:

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

Se vuoi impostare tutti i campi incondizionatamente, puoi usare il parametro $ multi , in questo modo: db.collection.update( { _id:...} , { $set: someObjectWithNewData, { $multi: true } } )
Villager

12
Non esiste un parametro multi $. C'è un'opzione multi (che è ciò a cui sei collegato), ma ciò non influisce sulla modalità di aggiornamento dei campi. Controlla se aggiorni un singolo documento o tutti i documenti che corrispondono al parametro della query.
Bob Kerns,

1
Questo non ha alcun senso. Quando si ha a che fare con mongo è preferibile anche che gli aggiornamenti abbiano operazioni atomiche. La prima lettura provoca una lettura sporca ogni volta che qualcun altro modifica i tuoi dati. E poiché mongo non ha transazioni, corromperà felicemente i dati per te.
froginvasion,

@froginvasion: puoi renderlo atomico usando i vecchi valori come filtro corrispondente sull'aggiornamento. In questo modo, l'aggiornamento fallirà se nel frattempo si verificava un conflitto simultaneo in conflitto. È quindi possibile rilevare ciò e agire di conseguenza. Questo si chiama blocco ottimistico. Ma sì, dipende dalla corretta applicazione dell'applicazione, Mongo stesso non ha transazioni integrate.
Thilo,

Penso che questa sia una domanda più generale di fusione di due oggetti in JavaScript. L'idea di IIRC è semplicemente assegnare le proprietà di quello nuovo al vecchio
information_interchange il

110

L'ho risolto con la mia funzione. Se si desidera aggiornare il campo specificato nel documento, è necessario affrontarlo chiaramente.

Esempio:

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

Se vuoi aggiornare solo param2, è sbagliato fare:

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

Devi usare:

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

Quindi ho scritto una funzione del genere:

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
Questa dovrebbe essere la risposta accettata. Per una migliore appiattimento della funzione Oggetto, vedere gist.github.com/penguinboy/762197
steph643

Grazie la tua risposta mi ha aiutato a guidarmi. Sono stato in grado di modificare il valore di una chiave specifica di un utente nella raccolta utenti come segue:db.users.update( { _id: ObjectId("594dbc3186bb5c84442949b1") }, { $set: { resume: { url: "http://i.imgur.com/UFX0yXs.jpg" } } } )
Luke Schoen,

Questa operazione è atomica e isolata? Significa che se 2 thread paralleli tentano di impostare 2 campi diversi dello stesso documento, ciò si tradurrà in condizioni di gara e risultati imprevedibili
so-random-dude

21

È possibile utilizzare la notazione a punti per accedere e impostare i campi in profondità all'interno degli oggetti, senza influire sulle altre proprietà di tali oggetti.

Dato l'oggetto sopra specificato:

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

Possiamo aggiornare solo some_key.param2e 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"
    }
}

Puoi approfondire quanto vuoi. Ciò è utile anche per aggiungere nuove proprietà a un oggetto senza influire su quelle esistenti.


Posso aggiungere una nuova chiave ... come "some key": {"param1": "val1", "param2": "val2_new", "param3": "val3_new", "param4": "val4_new",}
Urvashi Soni,

17

La soluzione migliore è estrarre le proprietà dall'oggetto e renderle coppie piatte chiave-notazione punto-notazione. È possibile utilizzare ad esempio questa libreria:

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

Ha una .flattenfunzione che ti permette di cambiare l'oggetto in un set piatto di proprietà che potrebbero essere date al modificatore $ set, senza preoccuparti che qualsiasi proprietà dell'oggetto DB esistente verrà eliminata / sovrascritta senza necessità.

Tratto da mongo-dot-notationdocumenti:

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

E quindi forma il selettore perfetto: aggiornerà SOLO le proprietà fornite. EDIT: Mi piace essere archeologo alcune volte;)



6

Ho avuto successo nel farlo in questo modo:

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

Ho una funzione che gestisce dinamicamente gli aggiornamenti del mio profilo

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

Nota: 'profilo' è specifico della mia implementazione, è solo la stringa della chiave che si desidera modificare.


2

Potresti piuttosto fare un upsert, questa operazione in MongoDB viene utilizzata per salvare il documento nella raccolta. Se il documento soddisfa i criteri di query, eseguirà l'operazione di aggiornamento, altrimenti inserirà un nuovo documento nella raccolta.

qualcosa di simile come di seguito

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


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

Se hai più campi da aggiornare

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

1

Iniziando Mongo 4.2, db.collection.update()può accettare una pipeline di aggregazione, che consente l'utilizzo di operatori di aggregazione come $addFields, che genera tutti i campi esistenti dai documenti di input e dai campi appena aggiunti:

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" } }
  • La prima parte {}è la query di corrispondenza, che filtra i documenti da aggiornare (in questo caso tutti i documenti).

  • La seconda parte [{ $addFields: { some_key: new_info } }]è la pipeline di aggregazione degli aggiornamenti:

    • Nota le parentesi quadre che indicano l'uso di una pipeline di aggregazione.
    • Dal momento che questa è una pipeline di aggregazione, possiamo usare $addFields.
    • $addFields esegue esattamente ciò di cui hai bisogno: aggiornare l'oggetto in modo che il nuovo oggetto si sovrapponga / si unisca a quello esistente:
    • In questo caso, { param2: "val2_new", param3: "val3_new" }verrà unito all'esistente some_keymantenendo param1intatto e aggiungendo o sostituendo entrambi param2e param3.
  • Non dimenticare { multi: true }, altrimenti verrà aggiornato solo il primo documento corrispondente.


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

per

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

Spero che questo aiuto!


0

usa $ set per eseguire questo processo

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

0

Crea un oggetto di aggiornamento con i nomi delle proprietà incluso il percorso del punto necessario. ("somekey." + dall'esempio di OP), quindi usa quell'aggiornamento.

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

Sì, il modo migliore è convertire la notazione oggetto in una rappresentazione di stringa valore-chiave piatta, come menzionato in questo commento: https://stackoverflow.com/a/39357531/2529199

Volevo evidenziare un metodo alternativo usando questa libreria NPM: https://www.npmjs.com/package/dot-object che ti consente di manipolare oggetti diversi usando la notazione a punti.

Ho usato questo modello per creare programmaticamente una proprietà di oggetto nidificato quando si accetta il valore-chiave come variabile di funzione, come segue:

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

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

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

Questo modello mi consente di utilizzare in modo intercambiabile le proprietà nidificate e quelle a livello singolo e di inserirle in modo pulito in Mongo.

Se devi giocare con JS Objects oltre a Mongo, specialmente sul lato client ma avere coerenza quando lavori con Mongo, questa libreria ti offre più opzioni rispetto al mongo-dot-notationmodulo NPM menzionato in precedenza .

PS Inizialmente volevo solo menzionarlo come commento, ma a quanto pare il mio rappresentante S / O non è abbastanza alto per pubblicare un commento. Quindi, non provando a dare forza al commento di SzybkiSasza, volevo solo evidenziare fornendo un modulo alternativo.



0

Dovresti pensare di aggiornare l'oggetto in modo intercambiabile e quindi semplicemente archiviare l'oggetto con i campi aggiornati. Qualcosa come fatto di seguito

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

Devi usare Embedded Documents (stringify l'oggetto path)

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

E così, in JavaScript è possibile utilizzare Spread Operator (...) per l'aggiornamento

db.collection.update(  { _id:...} , { $set: { ...update  } } 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.