更新数组 mongodb 内的嵌套数组
Updating nested array inside array 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);
}
});
MongoDB 3.6 及更高版本
MongoDB 3.6 及更高版本提供了一项新功能,允许您通过在更新语句中使用 the positional filtered $\[<identifier>\]
syntax in order to match the specific elements and apply different conditions through 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 doesn't support matching into more than one level of an array.
Consider altering your document model so each document represents an
operation, with information common to a set of operations duplicated
in the operation documents.
我同意上述观点并建议重新设计您的架构,因为 MongoDB 引擎不支持多个位置运算符(请参阅 Multiple use of the positional $
operator to update nested arrays)
但是,如果您事先知道具有要更新的参数对象的操作数组的索引,则更新查询将为:
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
键的键。
您的 reduce 步骤将是一个简单定义为 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"
}
}
);
从 MapReduce 操作查询输出集合 operations
通常会得到结果:
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
);
};
如果是变化频繁的数据,应该将结构扁平化,将变化大的数据和变化小的数据分开。
如果是不经常变化的数据,整个数据对象不是海量的,只需要在客户端修改对象,更新整个对象即可。
我们将尝试找到外部数组(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))
}
}
}
}
})
从 mongo 版本 3.6 开始,您可以结合使用 $[] 和 $[] 来更新嵌套数组
Update Nested Arrays in Conjunction with $[]
The $[] filtered positional operator, in conjunction with
all $[] positional operator can be used to update nested arrays.
我有以下 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);
}
});
MongoDB 3.6 及更高版本
MongoDB 3.6 及更高版本提供了一项新功能,允许您通过在更新语句中使用 the positional filtered $\[<identifier>\]
syntax in order to match the specific elements and apply different conditions through 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 doesn't support matching into more than one level of an array. Consider altering your document model so each document represents an operation, with information common to a set of operations duplicated in the operation documents.
我同意上述观点并建议重新设计您的架构,因为 MongoDB 引擎不支持多个位置运算符(请参阅 Multiple use of the positional $
operator to update nested arrays)
但是,如果您事先知道具有要更新的参数对象的操作数组的索引,则更新查询将为:
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
键的键。
您的 reduce 步骤将是一个简单定义为 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"
}
}
);
从 MapReduce 操作查询输出集合 operations
通常会得到结果:
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
);
};
如果是变化频繁的数据,应该将结构扁平化,将变化大的数据和变化小的数据分开。
如果是不经常变化的数据,整个数据对象不是海量的,只需要在客户端修改对象,更新整个对象即可。
我们将尝试找到外部数组(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))
}
}
}
}
})
从 mongo 版本 3.6 开始,您可以结合使用 $[] 和 $[] 来更新嵌套数组
Update Nested Arrays in Conjunction with $[]
The $[] filtered positional operator, in conjunction with all $[] positional operator can be used to update nested arrays.