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.