从具有多级数组的 json 创建一个复杂的 mongodb 查询
create a complex mongodb query from a json with multi-level array
我有这个 json :
{
"_id": "id",
"field1": "value1",
"field2": "value2",
"field3": "value3",
"field4": "value4",
"field5": "value5",
"field6": [
{
"field7": "value_a7_level1",
"field8": "value_a8_level1",
"field9": "value_a9_level1",
"field10": [
{
"field11": "value_a11_level1",
"field12": "value_a12_level1",
"field13": "value_a13_level1",
"field14": "value_a14_level1"
},
{
"field11": "value_b11_level1",
"field12": "value_b12_level1",
"field13": "value_b13_level1",
"field14": "value_b14_level1"
}
],
"field15": [
{
"field16": "zzz",
"field17": "xxx",
"field18": "value_a18_level1",
"field19": "value_a19_level1"
},
{
"field16": "xxx",
"field17": "yyy",
"field18": "value_b18_level1",
"field19": "value_b19_level1"
},
{
"field16": "xxx",
"field17": "yyy",
"field18": "value_c18_level1",
"field19": "value_c19_level1"
}
]
},
{
"field7": "value_a7_level2",
"field8": "value_a8_level2",
"field9": "value_a9_level2",
"field10": [
{
"field11": "value_a11_level2",
"field12": "value_a12_level2",
"field13": "value_a13_level2",
"field14": "value_a14_level2"
},
{
"field11": "value_b11_level2",
"field12": "value_b12_level2",
"field13": "value_b13_level2",
"field14": "value_b14_level2"
}
],
"field15": [
{
"field16": "value_a16_level2",
"field17": "value_a17_level2",
"field18": "value_a18_level2",
"field19": "value_a19_level2"
},
{
"field16": "value_b16_level2",
"field17": "value_b17_level2",
"field18": "value_b18_level2",
"field19": "value_b19_level2"
},
{
"field16": "value_c16_level2",
"field17": "value_c17_level2",
"field18": "value_c18_level2",
"field19": "value_c19_level2"
}
]
},
{
"field7": "value_a7_level3",
"field8": "value_a8_level3",
"field9": "value_a9_level3",
"field10": [
{
"field11": "value_a11_level3",
"field12": "value_a12_level3",
"field13": "value_a13_level3",
"field14": "value_a14_level3"
},
{
"field11": "value_b11_level3",
"field12": "value_b12_level3",
"field13": "value_b13_level3",
"field14": "value_b14_level3"
}
],
"field15": [
{
"field16": "xxx",
"field17": "yyy",
"field18": "value_a18_level3",
"field19": "value_a19_level3"
},
{
"field16": "value_b16_level3",
"field17": "value_b17_level3",
"field18": "value_b18_level3",
"field19": "value_b19_level3"
},
{
"field16": "value_c16_level3",
"field17": "value_c17_level3",
"field18": "value_c18_level3",
"field19": "value_c19_level3"
}
]
}
],
"field20": [
{
"field21": "value21_level1",
"field22": "value22_level1",
"field23": "value23_level1"
},
{
"field21": "value21_level2",
"field22": "value22_level2",
"field23": "value23_level2"
}
]
}
我想 return 根据过滤器和选择得到结果。
过滤器例如:
field16 和 field17 必须分别等于“XXX”和“YYY”。
我需要 return 一个 json 将包含所有字段,但 field15 将仅包含与过滤器对应的对象。此外,field6 将仅包含 chemp15 return 符合过滤器的对象。
在我的示例中,我将得到以下结果:
{
"_id": "id",
"field1": "value1",
"field2": "value2",
"field3": "value3",
"field4": "value4",
"field5": "value5",
"field6": [
{
"field7": "value_a7_level1",
"field8": "value_a8_level1",
"field9": "value_a9_level1",
"field10": [
{
"field11": "value_a11_level1",
"field12": "value_a12_level1",
"field13": "value_a13_level1",
"field14": "value_a14_level1"
},
{
"field11": "value_b11_level1",
"field12": "value_b12_level1",
"field13": "value_b13_level1",
"field14": "value_b14_level1"
}
],
"field15": [
{
"field16": "xxx",
"field17": "yyy",
"field18": "value_b18_level1",
"field19": "value_b19_level1"
},
{
"field16": "xxx",
"field17": "yyy",
"field18": "value_c18_level1",
"field19": "value_c19_level1"
}
]
},
{
"field7": "value_a7_level3",
"field8": "value_a8_level3",
"field9": "value_a9_level3",
"field10": [
{
"field11": "value_a11_level3",
"field12": "value_a12_level3",
"field13": "value_a13_level3",
"field14": "value_a14_level3"
},
{
"field11": "value_b11_level3",
"field12": "value_b12_level3",
"field13": "value_b13_level3",
"field14": "value_b14_level3"
}
],
"field15": [
{
"field16": "xxx",
"field17": "yyy",
"field18": "value_a18_level3",
"field19": "value_a19_level3"
}
]
}
],
"field20": [
{
"field21": "value21_level1",
"field22": "value22_level1",
"field23": "value23_level1"
},
{
"field21": "value21_level2",
"field22": "value22_level2",
"field23": "value23_level2"
}
]
}
我尝试了几种方法来得到这个结果,但都没有成功。这是最后一个,但我一点都不满意,因为结果结构不合理:
db.requirements.aggregate([
{
$match: {
"field6": {
$elemMatch: {
"field15": {
$elemMatch: {
"field16": "xxx",
"field17": "yyy",
}
}
}
}
}
},
{
$addFields: {
"field6": {
$map: {
input: "$field6",
as: "f6",
in: {
$filter: {
input: "$$f6.field15",
as: "f15",
cond: {$and: [
{$eq: ["$$f15.f16", "xxx"]},
{$eq: ["$$f15.f17", "yyy"]}
]}
}
}
}
}
},
}
]);
我也尝试使用 $unwind 和 $group,但它 return 不是我想要的 json。
谁能帮我找到解决办法?
预先感谢您的回答。
使用$reduce
作为循环。
db.foo.aggregate([
// In this strategy, we walk the field6 array with $reduce and
// "rebuild it" with either a filtered field15 or no entry at all.
{$addFields: {"field6": {$reduce: {
input: "$field6",
initialValue: [], // important: start rebuild with empty array
in: {$let: {
vars: {ee: {$filter: {input: "$$this.field15", as: "z",
cond: {$and:[ {$eq:["$$z.field16","xxx"]},
{$eq:["$$z.field17","yyy"]}
]}
}}
},
in: {$cond: [
{$ne:[0,{$size: "$$ee"}]}, // IF ee is not size 0
// THEN append an entry with filter field15
// plus its peer fields. Since we cannot directly
// say "$$this.field15 = $ee", we use $mergeObjects
// to overlay field15:$$ee onto the existing object
// with the peer fields field7, field8, field10, etc.
// $concatArrays wants arrays, not objects, so wrap
// it in [] to make an array of one:
{$concatArrays: [ "$$value",
[ {$mergeObjects: [ "$$this", {field15: "$$ee"} ]} ]
]},
// ELSE no concat; just pass back the existing array:
"$$value"
]}
}}
}}
}}
]);
或者,如果需要对 field6
应用更多条件,则可能更容易先在 field6
上 $unwind
以隔离对该文档内字段的进一步操作。但是请注意,如果 field6
是一个冗长的数组,$unwind
和 $group
可能会对性能产生影响。
db.foo.aggregate([
// Get us down to dealing with only one array:
{$unwind: "$field6"}
// Overwrite field6.field15 with filtered version of same:
,{$addFields: {"field6.field15":
{$filter: {input: "$field6.field15",
as: "z2",
cond: {$and:[ {$eq:["$$z2.field16","xxx"]},
{$eq:["$$z2.field17","yyy"]}
]}
}}
}}
// .. and eliminate those that have NO xxx/yyy in field16 and field17:
,{$match: {"field6.field15": {$ne:[]} }}
// You might be good enough at this point, but if you really want to reform the
// shape with an array for field6, use $group to put it back together.
// Using $first on all the other peer fields to field6 is a bit ungainly, yes, but
// it does produce the desired result:
,{$group: {_id:"$_id",
"field1": {$first: "$field1"},
"field2": {$first: "$field2"},
"field3": {$first: "$field3"},
"field4": {$first: "$field4"},
"field5": {$first: "$field5"},
"field6": {$push: "$field6"} // ah! Rebuild array
}}
]);
也许这就是您要找的:
db.collection.aggregate([
{
"$addFields": {
"field6": {
"$filter": {
"input": {
"$map": {
"input": "$field6",
"as": "f6",
"in": {
"$cond": [
true,
{
"field7": "$$f6.field7",
"field8": "$$f6.field8",
"field9": "$$f6.field9",
"field10": "$$f6.field10",
"field15": {
"$filter": {
"input": "$$f6.field15",
"as": "f15",
"cond": {
$and: [
{
$eq: [
"$$f15.field16",
"xxx"
]
},
{
$eq: [
"$$f15.field17",
"yyy"
]
}
]
}
}
}
},
false
]
}
}
},
"as": "cls",
"cond": {
$ne: [
"$$cls.field15",
[]
]
}
}
}
}
}
])
解释:
- 使用新的 filed6 创建一个 $addFields 阶段,它将覆盖基于原始 field6 上的地图过滤的旧文件
- 根据关于 f16 和 f17 的第二个过滤器向地图提供 filed15
- 从输出中移除第一个过滤器中的 f15 空数组,因为它们是在映射中为未找到匹配项的 f15 生成的
(当然你可以用 $elemMatch 添加你的初始 $match 阶段,我刚刚删除它以节省一些 space )
我有这个 json :
{
"_id": "id",
"field1": "value1",
"field2": "value2",
"field3": "value3",
"field4": "value4",
"field5": "value5",
"field6": [
{
"field7": "value_a7_level1",
"field8": "value_a8_level1",
"field9": "value_a9_level1",
"field10": [
{
"field11": "value_a11_level1",
"field12": "value_a12_level1",
"field13": "value_a13_level1",
"field14": "value_a14_level1"
},
{
"field11": "value_b11_level1",
"field12": "value_b12_level1",
"field13": "value_b13_level1",
"field14": "value_b14_level1"
}
],
"field15": [
{
"field16": "zzz",
"field17": "xxx",
"field18": "value_a18_level1",
"field19": "value_a19_level1"
},
{
"field16": "xxx",
"field17": "yyy",
"field18": "value_b18_level1",
"field19": "value_b19_level1"
},
{
"field16": "xxx",
"field17": "yyy",
"field18": "value_c18_level1",
"field19": "value_c19_level1"
}
]
},
{
"field7": "value_a7_level2",
"field8": "value_a8_level2",
"field9": "value_a9_level2",
"field10": [
{
"field11": "value_a11_level2",
"field12": "value_a12_level2",
"field13": "value_a13_level2",
"field14": "value_a14_level2"
},
{
"field11": "value_b11_level2",
"field12": "value_b12_level2",
"field13": "value_b13_level2",
"field14": "value_b14_level2"
}
],
"field15": [
{
"field16": "value_a16_level2",
"field17": "value_a17_level2",
"field18": "value_a18_level2",
"field19": "value_a19_level2"
},
{
"field16": "value_b16_level2",
"field17": "value_b17_level2",
"field18": "value_b18_level2",
"field19": "value_b19_level2"
},
{
"field16": "value_c16_level2",
"field17": "value_c17_level2",
"field18": "value_c18_level2",
"field19": "value_c19_level2"
}
]
},
{
"field7": "value_a7_level3",
"field8": "value_a8_level3",
"field9": "value_a9_level3",
"field10": [
{
"field11": "value_a11_level3",
"field12": "value_a12_level3",
"field13": "value_a13_level3",
"field14": "value_a14_level3"
},
{
"field11": "value_b11_level3",
"field12": "value_b12_level3",
"field13": "value_b13_level3",
"field14": "value_b14_level3"
}
],
"field15": [
{
"field16": "xxx",
"field17": "yyy",
"field18": "value_a18_level3",
"field19": "value_a19_level3"
},
{
"field16": "value_b16_level3",
"field17": "value_b17_level3",
"field18": "value_b18_level3",
"field19": "value_b19_level3"
},
{
"field16": "value_c16_level3",
"field17": "value_c17_level3",
"field18": "value_c18_level3",
"field19": "value_c19_level3"
}
]
}
],
"field20": [
{
"field21": "value21_level1",
"field22": "value22_level1",
"field23": "value23_level1"
},
{
"field21": "value21_level2",
"field22": "value22_level2",
"field23": "value23_level2"
}
]
}
我想 return 根据过滤器和选择得到结果。 过滤器例如:
field16 和 field17 必须分别等于“XXX”和“YYY”。
我需要 return 一个 json 将包含所有字段,但 field15 将仅包含与过滤器对应的对象。此外,field6 将仅包含 chemp15 return 符合过滤器的对象。
在我的示例中,我将得到以下结果:
{
"_id": "id",
"field1": "value1",
"field2": "value2",
"field3": "value3",
"field4": "value4",
"field5": "value5",
"field6": [
{
"field7": "value_a7_level1",
"field8": "value_a8_level1",
"field9": "value_a9_level1",
"field10": [
{
"field11": "value_a11_level1",
"field12": "value_a12_level1",
"field13": "value_a13_level1",
"field14": "value_a14_level1"
},
{
"field11": "value_b11_level1",
"field12": "value_b12_level1",
"field13": "value_b13_level1",
"field14": "value_b14_level1"
}
],
"field15": [
{
"field16": "xxx",
"field17": "yyy",
"field18": "value_b18_level1",
"field19": "value_b19_level1"
},
{
"field16": "xxx",
"field17": "yyy",
"field18": "value_c18_level1",
"field19": "value_c19_level1"
}
]
},
{
"field7": "value_a7_level3",
"field8": "value_a8_level3",
"field9": "value_a9_level3",
"field10": [
{
"field11": "value_a11_level3",
"field12": "value_a12_level3",
"field13": "value_a13_level3",
"field14": "value_a14_level3"
},
{
"field11": "value_b11_level3",
"field12": "value_b12_level3",
"field13": "value_b13_level3",
"field14": "value_b14_level3"
}
],
"field15": [
{
"field16": "xxx",
"field17": "yyy",
"field18": "value_a18_level3",
"field19": "value_a19_level3"
}
]
}
],
"field20": [
{
"field21": "value21_level1",
"field22": "value22_level1",
"field23": "value23_level1"
},
{
"field21": "value21_level2",
"field22": "value22_level2",
"field23": "value23_level2"
}
]
}
我尝试了几种方法来得到这个结果,但都没有成功。这是最后一个,但我一点都不满意,因为结果结构不合理:
db.requirements.aggregate([
{
$match: {
"field6": {
$elemMatch: {
"field15": {
$elemMatch: {
"field16": "xxx",
"field17": "yyy",
}
}
}
}
}
},
{
$addFields: {
"field6": {
$map: {
input: "$field6",
as: "f6",
in: {
$filter: {
input: "$$f6.field15",
as: "f15",
cond: {$and: [
{$eq: ["$$f15.f16", "xxx"]},
{$eq: ["$$f15.f17", "yyy"]}
]}
}
}
}
}
},
}
]);
我也尝试使用 $unwind 和 $group,但它 return 不是我想要的 json。
谁能帮我找到解决办法?
预先感谢您的回答。
使用$reduce
作为循环。
db.foo.aggregate([
// In this strategy, we walk the field6 array with $reduce and
// "rebuild it" with either a filtered field15 or no entry at all.
{$addFields: {"field6": {$reduce: {
input: "$field6",
initialValue: [], // important: start rebuild with empty array
in: {$let: {
vars: {ee: {$filter: {input: "$$this.field15", as: "z",
cond: {$and:[ {$eq:["$$z.field16","xxx"]},
{$eq:["$$z.field17","yyy"]}
]}
}}
},
in: {$cond: [
{$ne:[0,{$size: "$$ee"}]}, // IF ee is not size 0
// THEN append an entry with filter field15
// plus its peer fields. Since we cannot directly
// say "$$this.field15 = $ee", we use $mergeObjects
// to overlay field15:$$ee onto the existing object
// with the peer fields field7, field8, field10, etc.
// $concatArrays wants arrays, not objects, so wrap
// it in [] to make an array of one:
{$concatArrays: [ "$$value",
[ {$mergeObjects: [ "$$this", {field15: "$$ee"} ]} ]
]},
// ELSE no concat; just pass back the existing array:
"$$value"
]}
}}
}}
}}
]);
或者,如果需要对 field6
应用更多条件,则可能更容易先在 field6
上 $unwind
以隔离对该文档内字段的进一步操作。但是请注意,如果 field6
是一个冗长的数组,$unwind
和 $group
可能会对性能产生影响。
db.foo.aggregate([
// Get us down to dealing with only one array:
{$unwind: "$field6"}
// Overwrite field6.field15 with filtered version of same:
,{$addFields: {"field6.field15":
{$filter: {input: "$field6.field15",
as: "z2",
cond: {$and:[ {$eq:["$$z2.field16","xxx"]},
{$eq:["$$z2.field17","yyy"]}
]}
}}
}}
// .. and eliminate those that have NO xxx/yyy in field16 and field17:
,{$match: {"field6.field15": {$ne:[]} }}
// You might be good enough at this point, but if you really want to reform the
// shape with an array for field6, use $group to put it back together.
// Using $first on all the other peer fields to field6 is a bit ungainly, yes, but
// it does produce the desired result:
,{$group: {_id:"$_id",
"field1": {$first: "$field1"},
"field2": {$first: "$field2"},
"field3": {$first: "$field3"},
"field4": {$first: "$field4"},
"field5": {$first: "$field5"},
"field6": {$push: "$field6"} // ah! Rebuild array
}}
]);
也许这就是您要找的:
db.collection.aggregate([
{
"$addFields": {
"field6": {
"$filter": {
"input": {
"$map": {
"input": "$field6",
"as": "f6",
"in": {
"$cond": [
true,
{
"field7": "$$f6.field7",
"field8": "$$f6.field8",
"field9": "$$f6.field9",
"field10": "$$f6.field10",
"field15": {
"$filter": {
"input": "$$f6.field15",
"as": "f15",
"cond": {
$and: [
{
$eq: [
"$$f15.field16",
"xxx"
]
},
{
$eq: [
"$$f15.field17",
"yyy"
]
}
]
}
}
}
},
false
]
}
}
},
"as": "cls",
"cond": {
$ne: [
"$$cls.field15",
[]
]
}
}
}
}
}
])
解释:
- 使用新的 filed6 创建一个 $addFields 阶段,它将覆盖基于原始 field6 上的地图过滤的旧文件
- 根据关于 f16 和 f17 的第二个过滤器向地图提供 filed15
- 从输出中移除第一个过滤器中的 f15 空数组,因为它们是在映射中为未找到匹配项的 f15 生成的
(当然你可以用 $elemMatch 添加你的初始 $match 阶段,我刚刚删除它以节省一些 space )