MongoDB 3.6 et versions ultérieures
Avec MongoDB 3.6 et versions ultérieures, une nouvelle fonctionnalité vous permet de mettre à jour les tableaux imbriqués en utilisant le filtrage positionnel $\[<identifier>\]
syntaxe afin de faire correspondre les éléments spécifiques et d'appliquer différentes conditions via arrayFilters
dans la déclaration de mise à jour :
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);
}
});
Pour MongoDB 3.4 et versions antérieures :
Comme @wdberkeley l'a mentionné dans son commentaire :
MongoDB ne prend pas en charge la correspondance dans plus d'un niveau d'un tableau. Envisagez de modifier votre modèle de document afin que chaque document représente une opération, avec des informations communes à un ensemble d'opérations dupliquées dans les documents d'opération.
Je suis d'accord avec ce qui précède et je recommanderais de reconcevoir votre schéma car le moteur MongoDB ne prend pas en charge plusieurs opérateurs positionnels (voir Utilisation multiple du positionnel $
opérateur pour mettre à jour les tableaux imbriqués )
Cependant, si vous connaissez à l'avance l'index du tableau d'opérations contenant l'objet paramètres à mettre à jour, la requête de mise à jour sera :
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"
}
}
)
MODIF :
Si vous souhaitez créer le $set
conditions à la volée, c'est-à-dire quelque chose qui vous aiderait à obtenir les index des objets, puis à les modifier en conséquence, puis envisagez d'utiliser MapReduce .
Actuellement, cela ne semble pas possible en utilisant le cadre d'agrégation. Il y a un problème JIRA ouvert non résolu liée à celle-ci. Cependant, une solution de contournement est possible avec MapReduce . L'idée de base avec MapReduce est qu'il utilise JavaScript comme langage de requête, mais cela a tendance à être assez plus lent que le cadre d'agrégation et ne doit pas être utilisé pour l'analyse de données en temps réel.
Dans votre opération MapReduce, vous devez définir quelques étapes, c'est-à-dire l'étape de mappage (qui mappe une opération dans chaque document de la collection, et l'opération peut soit ne rien faire, soit émettre un objet avec des clés et des valeurs projetées) et l'étape de réduction ( qui prend la liste des valeurs émises et la réduit à un seul élément).
Pour l'étape de la carte, vous voudriez idéalement obtenir pour chaque document de la collection, l'index pour chaque operations
champ de tableau et une autre clé contenant le $set
clés.
Votre étape de réduction serait une fonction (qui ne fait rien) simplement définie comme var reduce = function() {};
La dernière étape de votre opération MapReduce créera ensuite une collection distincte d'opérations contenant l'objet de tableau d'opérations émis ainsi qu'un champ avec le $set
conditions. Cette collection peut être mise à jour périodiquement lorsque vous exécutez l'opération MapReduce sur la collection d'origine. Au total, cette méthode MapReduce ressemblerait à :
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"
}
}
);
Interrogation de la collection de sortie operations
de l'opération MapReduce vous donnera généralement le résultat :
db.operations.findOne()
Sortie :
{
"_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"
}
}
}
Vous pouvez ensuite utiliser le curseur de db.operations.find()
méthode pour itérer et mettre à jour votre collection en conséquence :
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
);
};