sql >> Base de Datos >  >> NoSQL >> MongoDB

Actualización de matriz anidada dentro de matriz mongodb

MongoDB 3.6 y posteriores

Con MongoDB 3.6 y versiones posteriores, viene una nueva función que le permite actualizar matrices anidadas mediante el uso del $\[<identifier>\] filtrado posicional. sintaxis para hacer coincidir los elementos específicos y aplicar diferentes condiciones a través de arrayFilters en la declaración de actualización:

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);
    }
});

Para MongoDB 3.4 y anteriores:

Como mencionó @wdberkeley en su comentario:

MongoDB no admite coincidencias en más de un nivel de una matriz. Considere modificar su modelo de documento para que cada documento represente una operación, con información común a un conjunto de operaciones duplicadas en los documentos de operación.

Estoy de acuerdo con lo anterior y recomendaría rediseñar su esquema ya que el motor MongoDB no admite múltiples operadores posicionales (consulte Uso múltiple del $ posicional operador para actualizar arreglos anidados )

Sin embargo, si conoce el índice de la matriz de operaciones que tiene el objeto de parámetros que se actualizará de antemano, la consulta de actualización será:

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" 
        }
    }
)

EDITAR:

Si desea crear el $set condiciones sobre la marcha, es decir, algo que lo ayudaría a obtener los índices de los objetos y luego modificarlos en consecuencia, luego considere usar MapReduce .

Actualmente, esto parece no ser posible utilizando el marco de agregación. Hay un problema de JIRA abierto sin resolver vinculado a él. Sin embargo, es posible una solución alternativa con MapReduce . La idea básica con MapReduce es que usa JavaScript como lenguaje de consulta, pero esto tiende a ser bastante más lento que el marco de agregación y no debe usarse para el análisis de datos en tiempo real.

En su operación MapReduce, necesita definir un par de pasos, es decir, el paso de mapeo (que mapea una operación en cada documento de la colección, y la operación puede no hacer nada o emitir algún objeto con claves y valores proyectados) y el paso de reducción ( que toma la lista de valores emitidos y la reduce a un solo elemento).

Para el paso del mapa, lo ideal sería obtener para cada documento de la colección, el índice para cada operations campo de matriz y otra clave que contiene el $set llaves.

Su paso de reducción sería una función (que no hace nada) simplemente definida como var reduce = function() {};

El paso final en su operación MapReduce creará una operación de colección separada que contiene el objeto de matriz de operaciones emitido junto con un campo con el $set condiciones. Esta colección se puede actualizar periódicamente cuando ejecuta la operación MapReduce en la colección original. En conjunto, este método MapReduce se vería así:

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"
        }
    }
);

Consultando la colección de salida operations de la operación MapReduce normalmente le dará el resultado:

db.operations.findOne()

Salida :

{
    "_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"
        }
    }
}

Luego puede usar el cursor desde db.operations.find() método para iterar y actualizar su colección en consecuencia:

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 
    );
};