猫鼬从嵌套的对象数组中获取确切的对象
Mongoose to get the exact object from a nested array of objects
我想使用 mongoose 查询从嵌套对象数组中检索确切的对象。
我已经使用 $elemMatch 尝试了一些查询,但没有得到我要定位的确切对象,而是我得到了对象数组。
我的 BSON 是
{
"_id": {
"$oid": "622bcb7a1091ddd45201258e"
},
"type": "Humans",
"person": [
{
"name": "Name Person 1",
"visited": [],
"_id": {
"$oid": "622bdjnmyi30e62d6d166ebd"
}
},
{
"name": "Name Person 2",
"visited": [
{
"country": "Country 1",
"year": "2022",
"id": "zuPks8cv3n"
}
],
"_id": {
"$oid": "622bdopmLks0e62d6d166ebe"
}
},
{
"name": "Name Person 3",
"_id": {
"$oid": "622bdpo8bnj0e62d6d166ebf"
},
"visited": [
{
"country": "Country 3",
"year": "2029",
"id": "l2Opx489xb"
},
{
"country": "Country 4",
"year": "2002",
"id": "s09zbHYjIp"
}
]
},
{
"name": "Name Person 4",
"visited": [],
"_id": {
"$oid": "622bdb9eio0sbt2d6d166ec0"
}
}
],
"__v": {
"$numberInt": "0"
}
}
我的目标是 “姓名 Person 3” 对象数组 “已访问” 和 ID 为 的对象“l2Opx489xb”.
我的预期结果是:
{
"country": "Country 3",
"year": "2029",
"id": "l2Opx489xb"
}
这是常见问题的一种变体,在这种情况下,尝试匹配数组中的单个项目会产生整个数组。问题不是“匹配”,因为匹配是正确的;这是投影的问题。找到匹配项后,我们只希望投影数组中的特定项目或一般文档的其余部分。
这是一个没有 $unwind
的单级解决方案。通常,在 $filter
、$map
和 $reduce
之间,可以在没有 $unwind
的情况下从单个文档中提取数据,这可能很昂贵。阅读“inside-out”中以#1 开头的评论。
db.foo.aggregate([
{$replaceRoot: {newRoot: // #7 ...and make this the root object
{$first: // #6 like #2, turn the array of 1 into a single object.
{$filter: { // #4 ... and now we filter the 'visited' array...
input: {$let: {
vars: {qq: {$first: // #2 $filter will yield an array of 0 or 1;
// use $first to turn into one object
// #1 Find Name Person 3
{$filter: {
input: '$person',
cond: {$eq:['$$this.name','Name Person 3']}
}}
}},
// #3 We wish we could say $first.visited in #2 but we cannot
// so we use $let and the vars setup to allow us to get to
// the inner array 'visited':
in: '$$qq.visited'}
},
cond: {$eq:['$$this.id','l2Opx489xb']} // #5 to match target id
}}
}
}}
]);
{ "country" : "Country 3", "year" : "2029", "id" : "l2Opx489xb" }
注意:$first
到达 v4.4。对于早期版本,而不是
{$first: <expression that yields array>}
改用这个:
{$arrayElemAt: [ <expression that yields array>, 0]
$first
更简洁一些,因为复杂表达式末尾没有 ,0
“悬挂”。
下面是“扩展”版本加上针对 $X
评估为 null
的额外检查(如果 Name Person 3
或目标 ID 不存在)因为 null
不能传递给 $replaceRoot
:
db.foo.aggregate([
{$project: {
X: {$first: {$filter: {
input: '$person',
cond: {$eq:['$$this.name','Name Person 3']}
}} }
}}
,{$project: {
X: {$first: {$filter: {
input: '$X.visited',
cond: {$eq:['$$this.id','l2Opx489xb']}
}} }
}}
,{$match: {X: {$ne: null}} }
,{$replaceRoot: {newRoot: '$X'}}
]);
OP 已将问题扩展为包括 1. 删除 visited
数组中的项目 2. 将对等字段更新为 visited
数组中的 ID 匹配。有两种方法:简单的和探索管道功能的一些强大功能的“艺术”。
简单
要更新年份和国家:
db.foo.update(
{'person.name':'Name Person 3'}
,{$set: {
'person.$[p].visited.$[v].year':'1999',
'person.$[p].visited.$[v].country':'ZOOP'
}}
,{arrayFilters: [ {'p.name':'Name Person 3'},{'v.id':'l2Opx489xb'} ] }
);
要从 visited
数组中完全删除目标 id 对象:
db.foo.update(
{'person.name':'Name Person 3'}
,{$pull: {'person.$.visited': {'id':'l2Opx489xb'}}}
);
ARTSY
rc=db.foo.update(
// Use dot notation for match to cut down initial input set.
// Remember: This will NOT find the exact offset into the
// array where Name Person 3 may be found, only that it is
// *somewhere* in that array. But this is good to filter out
// things where NO update is needed. The pipeline below will
// hunt for and remove the target id.
{'person.name':'Name Person 3'},
[{$set: { // pipeline form of update
// Use $reduce to walk the person array and essentially
// rebuild it but only operating on those entries where
// name is 'Name Person 3'
person: {$reduce: {
input: '$person',
initialValue: [],
// $concatArrays wants 2 arrays. The first is our ever-growing
// $$value. The second is a candidate object -- but we have to
// turn THAT into an array of 1, so watch for the [ ] wrapper around
// the outer $cond expression.
in:{$concatArrays: [ "$$value", [
{$cond: [
{$ne:['$$this.name','Name Person 3']}, // if NOT our target
'$$this', // THEN pass to concatArrays unchanged (noop)
// ELSE
// To DELETE the entire object:
// overwrite 'visited' with filtered version
// of same. This lets all other vars like name
// and anything else pass thru unchanged.
{$mergeObjects: [ '$$this',
{visited: {$filter: {
input: '$$this.visited',
as: 'qq',
cond: {$ne:['$$qq.id','l2Opx489xb']}
}}
}
]}
/*
To UPDATE year and country in the target, use
this instead instead of the $mergeObjects call
just above. Choose one or the other; both cannot
be active at same time.
{$mergeObjects: [ '$$this',
{visited: {$map: { // $map this time, not $filter
input: '$$this.visited',
as: 'qq',
in: {$cond:[
{$ne:['$$qq.id','l2Opx489xb']},
'$$qq', // THEN return unchanged
// ELSE overwrite year and country
// fields of the object:
{$mergeObjects: [ '$$qq',
{year:"1999",
country:"ZPONC"}
]}
]}
}}
}
]}
*/
]}
]] }
}}
}}
]
// ,{multi:true} // if Name Person 3 exists in more than 1 doc....
);
我想使用 mongoose 查询从嵌套对象数组中检索确切的对象。 我已经使用 $elemMatch 尝试了一些查询,但没有得到我要定位的确切对象,而是我得到了对象数组。
我的 BSON 是
{
"_id": {
"$oid": "622bcb7a1091ddd45201258e"
},
"type": "Humans",
"person": [
{
"name": "Name Person 1",
"visited": [],
"_id": {
"$oid": "622bdjnmyi30e62d6d166ebd"
}
},
{
"name": "Name Person 2",
"visited": [
{
"country": "Country 1",
"year": "2022",
"id": "zuPks8cv3n"
}
],
"_id": {
"$oid": "622bdopmLks0e62d6d166ebe"
}
},
{
"name": "Name Person 3",
"_id": {
"$oid": "622bdpo8bnj0e62d6d166ebf"
},
"visited": [
{
"country": "Country 3",
"year": "2029",
"id": "l2Opx489xb"
},
{
"country": "Country 4",
"year": "2002",
"id": "s09zbHYjIp"
}
]
},
{
"name": "Name Person 4",
"visited": [],
"_id": {
"$oid": "622bdb9eio0sbt2d6d166ec0"
}
}
],
"__v": {
"$numberInt": "0"
}
}
我的目标是 “姓名 Person 3” 对象数组 “已访问” 和 ID 为 的对象“l2Opx489xb”.
我的预期结果是:
{
"country": "Country 3",
"year": "2029",
"id": "l2Opx489xb"
}
这是常见问题的一种变体,在这种情况下,尝试匹配数组中的单个项目会产生整个数组。问题不是“匹配”,因为匹配是正确的;这是投影的问题。找到匹配项后,我们只希望投影数组中的特定项目或一般文档的其余部分。
这是一个没有 $unwind
的单级解决方案。通常,在 $filter
、$map
和 $reduce
之间,可以在没有 $unwind
的情况下从单个文档中提取数据,这可能很昂贵。阅读“inside-out”中以#1 开头的评论。
db.foo.aggregate([
{$replaceRoot: {newRoot: // #7 ...and make this the root object
{$first: // #6 like #2, turn the array of 1 into a single object.
{$filter: { // #4 ... and now we filter the 'visited' array...
input: {$let: {
vars: {qq: {$first: // #2 $filter will yield an array of 0 or 1;
// use $first to turn into one object
// #1 Find Name Person 3
{$filter: {
input: '$person',
cond: {$eq:['$$this.name','Name Person 3']}
}}
}},
// #3 We wish we could say $first.visited in #2 but we cannot
// so we use $let and the vars setup to allow us to get to
// the inner array 'visited':
in: '$$qq.visited'}
},
cond: {$eq:['$$this.id','l2Opx489xb']} // #5 to match target id
}}
}
}}
]);
{ "country" : "Country 3", "year" : "2029", "id" : "l2Opx489xb" }
注意:$first
到达 v4.4。对于早期版本,而不是
{$first: <expression that yields array>}
改用这个:
{$arrayElemAt: [ <expression that yields array>, 0]
$first
更简洁一些,因为复杂表达式末尾没有 ,0
“悬挂”。
下面是“扩展”版本加上针对 $X
评估为 null
的额外检查(如果 Name Person 3
或目标 ID 不存在)因为 null
不能传递给 $replaceRoot
:
db.foo.aggregate([
{$project: {
X: {$first: {$filter: {
input: '$person',
cond: {$eq:['$$this.name','Name Person 3']}
}} }
}}
,{$project: {
X: {$first: {$filter: {
input: '$X.visited',
cond: {$eq:['$$this.id','l2Opx489xb']}
}} }
}}
,{$match: {X: {$ne: null}} }
,{$replaceRoot: {newRoot: '$X'}}
]);
OP 已将问题扩展为包括 1. 删除 visited
数组中的项目 2. 将对等字段更新为 visited
数组中的 ID 匹配。有两种方法:简单的和探索管道功能的一些强大功能的“艺术”。
简单
要更新年份和国家:
db.foo.update(
{'person.name':'Name Person 3'}
,{$set: {
'person.$[p].visited.$[v].year':'1999',
'person.$[p].visited.$[v].country':'ZOOP'
}}
,{arrayFilters: [ {'p.name':'Name Person 3'},{'v.id':'l2Opx489xb'} ] }
);
要从 visited
数组中完全删除目标 id 对象:
db.foo.update(
{'person.name':'Name Person 3'}
,{$pull: {'person.$.visited': {'id':'l2Opx489xb'}}}
);
ARTSY
rc=db.foo.update(
// Use dot notation for match to cut down initial input set.
// Remember: This will NOT find the exact offset into the
// array where Name Person 3 may be found, only that it is
// *somewhere* in that array. But this is good to filter out
// things where NO update is needed. The pipeline below will
// hunt for and remove the target id.
{'person.name':'Name Person 3'},
[{$set: { // pipeline form of update
// Use $reduce to walk the person array and essentially
// rebuild it but only operating on those entries where
// name is 'Name Person 3'
person: {$reduce: {
input: '$person',
initialValue: [],
// $concatArrays wants 2 arrays. The first is our ever-growing
// $$value. The second is a candidate object -- but we have to
// turn THAT into an array of 1, so watch for the [ ] wrapper around
// the outer $cond expression.
in:{$concatArrays: [ "$$value", [
{$cond: [
{$ne:['$$this.name','Name Person 3']}, // if NOT our target
'$$this', // THEN pass to concatArrays unchanged (noop)
// ELSE
// To DELETE the entire object:
// overwrite 'visited' with filtered version
// of same. This lets all other vars like name
// and anything else pass thru unchanged.
{$mergeObjects: [ '$$this',
{visited: {$filter: {
input: '$$this.visited',
as: 'qq',
cond: {$ne:['$$qq.id','l2Opx489xb']}
}}
}
]}
/*
To UPDATE year and country in the target, use
this instead instead of the $mergeObjects call
just above. Choose one or the other; both cannot
be active at same time.
{$mergeObjects: [ '$$this',
{visited: {$map: { // $map this time, not $filter
input: '$$this.visited',
as: 'qq',
in: {$cond:[
{$ne:['$$qq.id','l2Opx489xb']},
'$$qq', // THEN return unchanged
// ELSE overwrite year and country
// fields of the object:
{$mergeObjects: [ '$$qq',
{year:"1999",
country:"ZPONC"}
]}
]}
}}
}
]}
*/
]}
]] }
}}
}}
]
// ,{multi:true} // if Name Person 3 exists in more than 1 doc....
);