MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Atualizando array aninhado dentro do array mongodb

MongoDB 3.6 e mais recente


Com o MongoDB 3.6 e superior vem um novo recurso que permite atualizar arrays aninhados usando o $\[<identifier>\] filtrado posicional sintaxe para corresponder aos elementos específicos e aplicar condições diferentes por meio de arrayFilters na declaração de atualização:
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 e versões anteriores:


Como @wdberkeley mencionou em seu comentário:

O MongoDB não suporta correspondência em mais de um nível de um array. Considere alterar seu modelo de documento para que cada documento represente uma operação, com informações comuns a um conjunto de operações duplicadas nos documentos de operação.

Concordo com o acima e recomendo redesenhar seu esquema, pois o mecanismo MongoDB não suporta vários operadores posicionais (consulte Múltiplo uso do $ posicional operador para atualizar matrizes aninhadas )

No entanto, se você conhece o índice da matriz de operações que possui o objeto de parâmetros a ser atualizado de antemão, a consulta de atualização 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:

Se você deseja criar o $set condições em tempo real, ou seja, algo que ajudaria você a obter os índices para os objetos e, em seguida, modificar de acordo, considere usar MapReduce .

Atualmente, isso parece não ser possível usando a estrutura de agregação. Há um problema do JIRA aberto não resolvido ligado a ele. No entanto, uma solução alternativa é possível com MapReduce . A ideia básica do MapReduce é que ele usa JavaScript como sua linguagem de consulta, mas isso tende a ser bastante mais lento do que a estrutura de agregação e não deve ser usado para análise de dados em tempo real.

Em sua operação MapReduce, você precisa definir algumas etapas, ou seja, a etapa de mapeamento (que mapeia uma operação em todos os documentos da coleção, e a operação pode não fazer nada ou emitir algum objeto com chaves e valores projetados) e etapa de redução ( que pega a lista de valores emitidos e a reduz a um único elemento).

Para a etapa do mapa, o ideal é obter para cada documento da coleção, o índice de cada operations campo array e outra chave que contém o $set chaves.

Sua etapa de redução seria uma função (que não faz nada) simplesmente definida como var reduce = function() {};

A etapa final em sua operação MapReduce criará então uma coleção de operações separada que contém o objeto array de operações emitidas junto com um campo com o $set condições. Essa coleção pode ser atualizada periodicamente quando você executa a operação MapReduce na coleção original. Ao todo, esse método MapReduce ficaria assim:
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 a coleção de saída operations da operação MapReduce normalmente fornecerá o resultado:
db.operations.findOne()

Saída :
{
    "_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"
        }
    }
}

Você pode então usar o cursor do db.operations.find() método para iterar e atualizar sua coleção de acordo:
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 
    );
};