[英]MongoDB Aggregation - match documents with array of objects, by another array of objects filter
我有由对象数组组成的文档,并且该数组中的每个对象都由另一个对象数组组成。
为简单起见,省略了文档中不相关的字段。
它看起来像这样(2 个文档):
{
title: 'abc',
parts: [
{
part: "verse",
progressions: [
{
progression: "62a4a87da7fdbdabf787e47f",
key: "Ab",
_id: "62b5aaa0c9e9fe8a7d7240d3"
},
{
progression: "62adf477ed11cbbe156d5769",
key: "C",
_id: "62b5aaa0c9e9fe8a7d7240d3"
},
],
_id: "62b5aaa0c9e9fe8a7d7240d2"
},
{
part: "chorus",
progressions: [
{
progression: "62a4a51b4693c43dce9be09c",
key: "E",
_id: "62b5aaa0c9e9fe8a7d7240d9"
}
],
_id: "62b5aaa0c9e9fe8a7d7240d8"
}
],
}
{
title: 'def',
parts: [
{
part: "verse",
progressions: [
{
progression: "33a4a87da7fopvvbf787erwe",
key: "E",
_id: "62b5aaa0c9e9fe8a7d7240d3"
},
{
progression: "98opf477ewfscbbe156d5442",
key: "Bb",
_id: "62b5aaa0c9e9fe8a7d7240d3"
},
],
_id: "12r3aaa0c4r5me8a7d72oi8u"
},
{
part: "bridge",
progressions: [
{
progression: "62a4a51b4693c43dce9be09c",
key: "C#",
_id: "62b5aaa0c9e9fe8a7d7240d9"
}
],
_id: "62b5aaa0rwfvse8a7d7240d8"
}
],
}
客户端随请求发送的参数是一个对象数组:
[
{ part: 'verse', progressions: ['62a4a87da7fdbdabf787e47f', '62a4a51b4693c43dce9be09c'] },
{ part: 'chorus', progressions: ['62adf477ed11cbbe156d5769'] }
]
我想通过 mongodb 聚合检索上面输入数组中至少一个对象与它们匹配的文档:
在此示例中,文档在其parts 数组字段中具有在part 属性中具有值“verse”的对象,并且在其中一个对象的progress 属性中具有进程id 的['62a4a87da7fdbdabf787e47f', '62a4a51b4693c43dce9be09c'] 之一在progresss 属性中,或者在其parts 数组字段中具有值的文档中,在part 属性中具有值“chorus”的对象和progress 中的一个对象的progress 属性中的progresss id 之一['62adf477ed11cbbe156d5769']级数属性。
在这个例子中,匹配的文档是第一个(标题为'abc'),但在实际使用中,匹配的文档可能很多。
我尝试自己创建一个聚合管道(使用 mongoose 'aggregate' 方法):
// parsedProgressions = [
// { part: 'verse', progressions: ['62a4a87da7fdbdabf787e47f', '62a4a51b4693c43dce9be09c'] },
// { part: 'chorus', progressions: ['62adf477ed11cbbe156d5769'] }
// ]
songs.aggregate([
{
$addFields: {
"tempMapResults": {
$map: {
input: parsedProgressions,
as: "parsedProgression",
in: {
$cond: {
if: { parts: { $elemMatch: { part: "$$parsedProgression.part", "progressions.progression": mongoose.Types.ObjectId("$$parsedProgression.progression") } } },
then: true, else: false
}
}
}
}
}
},
{
$addFields: {
"isMatched": { $anyElementTrue: ["$tempMapResults"] }
}
},
{ $match: { isMatched: true } },
{ $project: { title: 1, "parts.part": 1, "parts.progressions.progression": 1 } }
]);
但它不起作用 - 据我了解,因为 $elemMatch 只能在 $match 阶段使用。
无论如何,我想我把聚合管道过度复杂化了,所以如果你能修复我的聚合管道/提供一个更好的工作管道,我会很高兴。
这不是一个简单的情况,因为它们都是嵌套数组,我们需要匹配part
和progressions
,它们不在同一级别
一个选项看起来有点复杂,但可以让你的数据变小:
$set
一个名为matchCond
的新数组字段,其中包括一个名为progs
的数组,其中包含parts.progressions
。 向其中的每个子对象插入匹配的progressions
输入数组。 我们确实需要在这里小心并处理没有匹配的progressions
输入数组progressions
输入数组的情况,因为这是第二个文档中的“桥”部分的情况。progs
项目中的任何一个, progression
字段是否与input
数组中的一个选项匹配。 这是使用$filter
和$redice
来完成结果的数量。db.collection.aggregate([
{
$set: {
matchCond: {
$map: {
input: "$parts",
as: "parts",
in: {progs: {
$map: {
input: "$$parts.progressions",
in: {$mergeObjects: [
"$$this",
{input: {progressions: []}},
{input: {$first: {
$filter: {
input: inputData,
as: "inputPart",
cond: {$eq: ["$$inputPart.part", "$$parts.part"]}
}
}}}
]}
}
}}
}
}
}
},
{$set: {
matchCond: {
$reduce: {
input: "$matchCond",
initialValue: 0,
in: {$add: [
"$$value",
{$size: {
$filter: {
input: "$$this.progs",
as: "part",
cond: {$in: ["$$part.progression", "$$part.input.progressions"]}
}
}
}
]
}
}
}
}
},
{$match: {matchCond: {$gt: 0}}},
{$project: {title: 1, parts: 1}}
])
看看它在操场上的例子是如何工作的
另一种选择是使用$unwind
,它看起来很简单,但会复制您的数据,因此可能会更慢:
db.collection.aggregate([
{$addFields: {inputData: inputData, cond: "$parts"}},
{$unwind: "$cond"},
{$unwind: "$cond.progressions"},
{$unwind: "$inputData"},
{$match: {
$expr: {
$and: [
{$eq: ["$cond.part", "$inputData.part"]},
{$in: ["$cond.progressions.progression", "$inputData.progressions"]}
]
}
}
},
{$project: {title: 1, parts: 1}}
])
这两者之间有几种选择...
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.