MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Aggiornamento dell'array nidificato all'interno dell'array mongodb

MongoDB 3.6 e versioni successive

Con MongoDB 3.6 e versioni successive arriva una nuova funzionalità che ti consente di aggiornare gli array nidificati utilizzando il filtro posizionale $\[<identifier>\] sintassi per abbinare gli elementi specifici e applicare condizioni diverse tramite arrayFilters nella dichiarazione di aggiornamento:

const { oid, pid } = req.params;
const { name, oName, description, type } = req.body; 

collection.update(
    {
        "_id": 1,
        "operations": {
            "$elemMatch": {
                oid, "parameters.pid": pid
            }
        }
    },
    { "$set": { 
        "operations.$[outer].parameters.$[inner].name": name,
        "operations.$[outer].parameters.$[inner].description": description,
        "operations.$[outer].parameters.$[inner].oName": oName,
        "operations.$[outer].parameters.$[inner].type": type 
    } },
    { "arrayFilters": [
        { "outer.oid": oid },
        { "inner.pid": pid }
    ] }, (err, result) => {
    if (err) {
        console.log('Error updating service: ' + err);
        res.send({'error':'An error has occurred'});
    } else {
        // console.log('' + result + ' document(s) updated');
        res.send(result);
    }
});

Per MongoDB 3.4 e versioni precedenti:

Come menzionato da @wdberkeley nel suo commento:

MongoDB non supporta la corrispondenza in più di un livello di un array. Considera la possibilità di modificare il modello del tuo documento in modo che ogni documento rappresenti un'operazione, con informazioni comuni a un insieme di operazioni duplicate nei documenti operativi.

Concordo con quanto sopra e consiglierei di riprogettare il tuo schema poiché il motore MongoDB non supporta più operatori posizionali (vedi Utilizzo multiplo del $ posizionale) operatore per aggiornare gli array nidificati )

Tuttavia, se si conosce l'indice dell'array delle operazioni che ha l'oggetto parametri da aggiornare in anticipo, la query di aggiornamento sarà:

db.collection.update(
    {
        "_id" : "04", 
        "operations.parameters.pid": "011"
    }, 
    {
        "$set": { 
            "operations.0.parameters.$.name": "foo",
            "operations.0.parameters.$.description": "bar", 
            "operations.0.parameters.$.type": "foo" 
        }
    }
)

MODIFICA:

Se desideri creare il $set condizioni al volo, ovvero qualcosa che ti aiuterebbe a ottenere gli indici per gli oggetti e quindi a modificarli di conseguenza, quindi considera l'utilizzo di MapReduce .

Attualmente questo sembra non essere possibile utilizzando il framework di aggregazione. C'è un problema JIRA aperto non risolto legato ad esso. Tuttavia, è possibile una soluzione alternativa con MapReduce . L'idea di base con MapReduce è che utilizza JavaScript come linguaggio di query, ma questo tende ad essere abbastanza più lento del framework di aggregazione e non dovrebbe essere utilizzato per l'analisi dei dati in tempo reale.

Nella tua operazione MapReduce, devi definire un paio di passaggi, ad esempio il passaggio di mappatura (che mappa un'operazione in ogni documento nella raccolta e l'operazione non può fare nulla o emettere qualche oggetto con chiavi e valori proiettati) e il passaggio di riduzione ( che prende l'elenco dei valori emessi e lo riduce a un unico elemento).

Per il passaggio della mappa, idealmente vorresti ottenere per ogni documento nella raccolta, l'indice per ogni operations campo array e un'altra chiave che contiene il $set chiavi.

Il tuo passaggio di riduzione sarebbe una funzione (che non fa nulla) semplicemente definita come var reduce = function() {};

Il passaggio finale dell'operazione MapReduce creerà quindi una raccolta separata di operazioni che contiene l'oggetto array delle operazioni emesso insieme a un campo con il $set condizioni. Questa raccolta può essere aggiornata periodicamente quando esegui l'operazione MapReduce sulla raccolta originale. Nel complesso, questo metodo MapReduce sarebbe simile a:

var map = function(){
    for(var i = 0; i < this.operations.length; i++){
        emit( 
            {
                "_id": this._id, 
                "index": i 
            }, 
            {
                "index": i, 
                "operations": this.operations[i],            
                "update": {
                    "name": "operations." + i.toString() + ".parameters.$.name",
                    "description": "operations." + i.toString() + ".parameters.$.description",
                    "type": "operations." + i.toString() + ".parameters.$.type"
                }                    
            }
        );
    }
};

var reduce = function(){};

db.collection.mapReduce(
    map,
    reduce,
    {
        "out": {
            "replace": "operations"
        }
    }
);

Interrogazione della raccolta di output operations dall'operazione MapReduce in genere ti darà il risultato:

db.operations.findOne()

Risultato :

{
    "_id" : {
        "_id" : "03",
        "index" : 0
    },
    "value" : {
        "index" : 0,
        "operations" : {
            "_id" : "96",
            "oName" : "test op 52222222222",
            "sid" : "04",
            "name" : "test op 52222222222",
            "oid" : "99",
            "description" : "testing",
            "returntype" : "test",
            "parameters" : [ 
                {
                    "oName" : "Param1",
                    "name" : "foo",
                    "pid" : "011",
                    "type" : "foo",
                    "description" : "bar",
                    "value" : ""
                }, 
                {
                    "oName" : "Param2",
                    "name" : "Param2",
                    "pid" : "012",
                    "type" : "58222",
                    "description" : "testing",
                    "value" : ""
                }
            ]
        },
        "update" : {
            "name" : "operations.0.parameters.$.name",
            "description" : "operations.0.parameters.$.description",
            "type" : "operations.0.parameters.$.type"
        }
    }
}

È quindi possibile utilizzare il cursore da db.operations.find() metodo per scorrere e aggiornare la tua raccolta di conseguenza:

var oid = req.params.operations;
var pid = req.params.parameters;
var cur = db.operations.find({"_id._id": oid, "value.operations.parameters.pid": pid });

// Iterate through results and update using the update query object set dynamically by using the array-index syntax.
while (cur.hasNext()) {
    var doc = cur.next();
    var update = { "$set": {} };
    // set the update query object
    update["$set"][doc.value.update.name] = req.body.name;
    update["$set"][doc.value.update.description] = req.body.description;
    update["$set"][doc.value.update.type] = req.body.type;

    db.collection.update(
        {
            "_id" : oid, 
            "operations.parameters.pid": pid
        }, 
        update 
    );
};