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