MongoDB 3.6 og nyere
Med MongoDB 3.6 og nyere kommer en ny funktion, der giver dig mulighed for at opdatere indlejrede arrays ved at bruge den positionsfiltrerede $\[<identifier>\]
syntaks for at matche de specifikke elementer og anvende forskellige betingelser gennem arrayFilters
i opdateringserklæringen:
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);
}
});
For MongoDB 3.4 og ældre:
Som @wdberkeley nævnte i sin kommentar:
MongoDB understøtter ikke matchning til mere end ét niveau af et array. Overvej at ændre din dokumentmodel, så hvert dokument repræsenterer en operation med information, der er fælles for et sæt operationer, duplikeres i operationsdokumenterne.
Jeg er enig i ovenstående og vil anbefale at redesigne dit skema, da MongoDB-motoren ikke understøtter flere positionsoperatorer (se Multiple brug af den positionelle $
operator for at opdatere indlejrede arrays )
Men hvis du kender indekset for operationsarrayet, der har parameterobjektet, der skal opdateres på forhånd, vil opdateringsforespørgslen være:
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"
}
}
)
EDIT:
Hvis du gerne vil oprette $set
betingelser i farten, dvs. noget, der vil hjælpe dig med at få indekserne for objekterne og derefter ændre i overensstemmelse hermed, og overvej derefter at bruge MapReduce .
I øjeblikket synes dette ikke at være muligt ved brug af aggregeringsrammen. Der er et uløst åbent JIRA-problem knyttet til det. En løsning er dog mulig med MapReduce . Den grundlæggende idé med MapReduce er, at den bruger JavaScript som forespørgselssprog, men dette har en tendens til at være ret langsommere end aggregeringsrammen og bør ikke bruges til dataanalyse i realtid.
I din MapReduce-operation skal du definere et par trin, dvs. mapping-trinnet (som kortlægger en operation i hvert dokument i samlingen, og operationen kan enten ikke gøre noget eller udsende et objekt med nøgler og projicerede værdier) og reducerende trin ( som tager listen over udsendte værdier og reducerer den til et enkelt element).
Til korttrinnet vil du ideelt set have for hvert dokument i samlingen indekset for hver operations
array-felt og en anden nøgle, der indeholder $set
nøgler.
Dit reduktionstrin ville være en funktion (som ikke gør noget) blot defineret som var reduce = function() {};
Det sidste trin i din MapReduce-operation vil derefter oprette en separat indsamlingsoperation, der indeholder det udsendte operations-array-objekt sammen med et felt med $set
betingelser. Denne samling kan opdateres med jævne mellemrum, når du kører MapReduce-handlingen på den originale samling. Alt i alt vil denne MapReduce-metode se sådan ud:
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"
}
}
);
Forespørger om outputsamlingens operations
fra MapReduce-operationen vil typisk give dig resultatet:
db.operations.findOne()
Output :
{
"_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"
}
}
}
Du kan derefter bruge markøren fra db.operations.find()
metode til at gentage og opdatere din samling i overensstemmelse hermed:
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
);
};