MongoDB 3.6 и по-нова версия
С MongoDB 3.6 и по-нова версия идва нова функция, която ви позволява да актуализирате вложени масиви, като използвате филтрирания по позиция $\[<identifier>\]
синтаксис, за да се съпоставят конкретните елементи и да се прилагат различни условия чрез arrayFilters
в изявлението за актуализиране:
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);
}
});
За MongoDB 3.4 и по-стари:
Както @wdberkeley спомена в коментара си:
MongoDB не поддържа съвпадение в повече от едно ниво на масив. Помислете за промяна на модела на вашия документ, така че всеки документ да представлява операция, с информация, обща за набор от операции, дублирани в оперативните документи.
Съгласен съм с горното и бих препоръчал да преработите схемата си, тъй като механизмът на MongoDB не поддържа множество позиционни оператори (вижте Многократно използване на позиционния $
оператор за актуализиране на вложени масивии )
Въпреки това, ако знаете индекса на масива от операции, който има обекта на параметрите, който трябва да бъде актуализиран предварително, тогава заявката за актуализиране ще бъде:
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"
}
}
)
РЕДАКТИРАНЕ:
Ако искате да създадете $set
условия в движение, т.е. нещо, което ще ви помогне да получите индексите за обектите и след това да промените съответно, след което помислете за използването на MapReduce .
Понастоящем това изглежда не е възможно с помощта на рамката за агрегиране. Има неразрешен отворен проблем JIRA свързан с него. Въпреки това е възможно заобиколно решение с MapReduce . Основната идея с MapReduce е, че той използва JavaScript като свой език за заявки, но това е доста по-бавно от рамката за агрегиране и не трябва да се използва за анализ на данни в реално време.
Във вашата операция MapReduce трябва да дефинирате няколко стъпки, т.е. стъпката на картографиране (която преобразува операция във всеки документ в колекцията и операцията може или да не прави нищо, или да излъчва някакъв обект с ключове и прогнозирани стойности) и стъпка за намаляване ( който взема списъка с излъчени стойности и го свежда до един елемент).
За стъпката на картата в идеалния случай бихте искали да получите за всеки документ в колекцията, индекса за всяка operations
поле на масив и друг ключ, който съдържа $set
ключове.
Вашата стъпка за намаляване ще бъде функция (която не прави нищо), просто дефинирана като var reduce = function() {};
Последната стъпка във вашата операция MapReduce ще създаде отделни операции за събиране, които съдържат обекта на масива на излъчените операции заедно с поле с $set
условия. Тази колекция може да се актуализира периодично, когато стартирате операцията MapReduce върху оригиналната колекция. Като цяло този метод MapReduce ще изглежда така:
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"
}
}
);
Запитване на изходната колекция operations
от операцията MapReduce обикновено ще ви даде резултат:
db.operations.findOne()
Изход :
{
"_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"
}
}
}
След това можете да използвате курсора от db.operations.find()
метод за повторение и съответно актуализиране на вашата колекция:
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
);
};