Обновление вложенного массива внутри массива mongodb
У меня есть следующая структура документа mongodb:
[
{
"_id": "04",
"name": "test service 4",
"id": "04",
"version": "0.0.1",
"title": "testing",
"description": "test",
"protocol": "test",
"operations": [
{
"_id": "99",
"oName": "test op 52222222222",
"sid": "04",
"name": "test op 52222222222",
"oid": "99",
"description": "testing",
"returntype": "test",
"parameters": [
{
"oName": "Param1",
"name": "Param1",
"pid": "011",
"type": "582",
"description": "testing",
"value": ""
},
{
"oName": "Param2",
"name": "Param2",
"pid": "012",
"type": "58222",
"description": "testing",
"value": ""
}
]
}
]
}
]
Я был в состоянии использовать $elemMatch для обновления полей в операциях, но когда я пытаюсь сделать то же самое (изменено) для параметров, это, кажется, не работает. Мне было интересно, какой другой подход я должен рассмотреть, чтобы иметь возможность успешно обновлять поля в определенном параметре, просматривая его по pid.
Код обновления, который у меня сейчас есть и не работает, выглядит так:
var oid = req.params.operations;
var pid = req.params.parameters;
collection.update({"parameters":{"$elemMatch": {"pid": pid}}},{"$set": {"parameters.$.name":req.body.name, "parameters.$.description": req.body.description,"parameters.$.oName": req.body.oName,"parameters.$.type": req.body.type} }, function(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);
}
});
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
);
};
Если это данные, которые часто изменяются, вы должны сгладить структуру и отделить данные, которые сильно изменяются, от данных, которые этого не делают.
Если это данные, которые не изменяются часто, и весь объект данных не массивен, просто измените объект на стороне клиента и обновите весь объект.
Начиная с версии 3.6, вы можете использовать $ [] в соединении с $ [] для обновления вложенных массивов.
Обновить вложенные массивы в сочетании с $ []
Фильтрованный позиционный оператор $ [] вместе со всем позиционным оператором $ [] может использоваться для обновления вложенных массивов.
https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/
Мы попытаемся найти индекс внешнего массива (i) и внутреннего массива (j), а затем обновим
collection.findById(04)
.then(result =>{
for(let i = 0; i<result.operations.length; i++){
if(result.operation[i]._id == "99"){
let parameters = result.operations[i].parameters;`enter code here`
for(let j = 0; j<parameters.length; j++){
if(parameters[j].pid == "011"){
console.log("i", i);
console.log("j", j);
let data = {}
data["operations." + i + ".parameters." + j + ".oName"] = updateoName
data["operations." + i + ".parameters." + j + ".name"] = updatename
data["operations." + i + ".parameters." + j + ".pid"] = updatepid
data["operations." + i + ".parameters." + j + ".description"] = updatedescription
data["operations." + i + ".parameters." + j + ".value"] = updatevalue
console.log(data)
collection.update({
"_id": "04"
},{
$set: data
})
.then(dbModel => res.json(dbModel))
}
}
}
}
})