MongoDB 3.6 och nyare
Med MongoDB 3.6 och senare kommer en ny funktion som låter dig uppdatera kapslade arrayer genom att använda den positionsfiltrerade $\[<identifier>\]
syntax för att matcha de specifika elementen och tillämpa olika villkor genom arrayFilters
i uppdateringsmeddelandet:
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);
}
});
För MongoDB 3.4 och äldre:
Som @wdberkeley nämnde i sin kommentar:
MongoDB stöder inte matchning till mer än en nivå av en array. Överväg att ändra din dokumentmodell så att varje dokument representerar en operation, med information som är gemensam för en uppsättning operationer dupliceras i operationsdokumenten.
Jag instämmer i ovanstående och rekommenderar att du gör om ditt schema eftersom MongoDB-motorn inte stöder flera positionsoperatorer (se Fler användning av positions $
operator för att uppdatera kapslade arrayer )
Men om du känner till indexet för operationsarrayen som har parameterobjektet som ska uppdateras i förväg, kommer uppdateringsfrågan att vara:
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:
Om du vill skapa $set
förhållanden i farten, det vill säga något som skulle hjälpa dig att få indexen för objekten och sedan ändra därefter, överväg sedan att använda MapReduce .
För närvarande verkar detta inte vara möjligt med hjälp av aggregeringsramverket. Det finns ett olöst öppet JIRA-problem kopplat till det. En lösning är dock möjlig med MapReduce . Grundidén med MapReduce är att den använder JavaScript som frågespråk, men detta tenderar att vara ganska långsammare än aggregeringsramverket och bör inte användas för dataanalys i realtid.
I din MapReduce-operation måste du definiera ett par steg, dvs mappningssteget (som mappar en operation till varje dokument i samlingen, och operationen kan antingen inte göra någonting eller sända ut något objekt med nycklar och projicerade värden) och reduceringssteget ( som tar listan över emitterade värden och reducerar den till ett enda element).
För kartsteget skulle du helst vilja ha för varje dokument i samlingen, indexet för varje operations
arrayfält och en annan nyckel som innehåller $set
nycklar.
Ditt reduceringssteg skulle vara en funktion (som inte gör någonting) helt enkelt definierad som var reduce = function() {};
Det sista steget i din MapReduce-operation kommer sedan att skapa en separat insamlingsoperation som innehåller det emitterade operations-arrayobjektet tillsammans med ett fält med $set
betingelser. Den här samlingen kan uppdateras med jämna mellanrum när du kör MapReduce-operationen på den ursprungliga samlingen. Sammantaget skulle den här MapReduce-metoden se ut så här:
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"
}
}
);
Frågar utdatasamlingens operations
från MapReduce-operationen ger dig vanligtvis resultatet:
db.operations.findOne()
Utdata :
{
"_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 sedan använda markören från db.operations.find()
metod för att upprepa och uppdatera din samling i enlighet med detta:
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
);
};