我有两个系列:
用户
{
id: 1,
name: "Michael",
starred: [1, 2]
}
学校
{
id: 1,
name: "Uni",
faculties: [{
id:1000,
name: "faculty1",
subjects: [
{id: 1, name: "sub1"},
{id: 2, name: "sub2"},
{id: 3, name: "sub3"}
]
}]
}
现在,在我的用户集合中,我希望使用已加星标中的ID查找并收集每个主题对象。即。 starred: [1,2]
包含我想要的id
个主题。
所以最终结果应该返回
[{id: 1, name: sub1},{id: 2, name: sub2}]
我目前正在使用whis聚合管道
{$match: {name: 'Michael'}},
{$unwind: "$faculties"},
{$unwind: "$faculties.subjects"},
{$lookup:
{
from: 'schools',
localField: 'starred',
foreignField: 'faculties.subjects.id',
as: 'starredSubjects'
}
},
{$project: {starredSubjects: 1}}
但展开不起作用(我猜是因为我试图解开一个外国集合,而不是本地集合(即用户)。
foreignField: 'faculties.subjects.id
也不会返回任何内容。我错过了什么?
(旁注:MongoExplorer webstorm插件的测试很棒)。
答案 0 :(得分:3)
这真的不是一个很好的结构,并且有很好的理由。因此,在这里执行$lookup
并不是一项简单的任务,因为“嵌套数组”存在各种含义
你基本上想要
db.users.aggregate([
{ "$match": { "name": "Michael" } },
{ "$lookup": {
"from": "schools",
"localField": "starred",
"foreignField": "faculties.subjects.id",
"as": "subjects"
}},
{ "$addFields": {
"subjects": {
"$filter": {
"input": {
"$reduce": {
"input": {
"$reduce": {
"input": "$subjects.faculties.subjects",
"initialValue": [],
"in": { "$concatArrays": [ "$$value", "$$this" ] }
}
},
"initialValue": [],
"in": { "$concatArrays": [ "$$value", "$$this" ] }
}
},
"cond": { "$in": ["$$this.id", "$starred"] }
}
}
}}
])
或者使用MongoDB 3.6或更高版本:
db.users.aggregate([
{ "$match": { "name": "Michael" } },
{ "$lookup": {
"from": "schools",
"let": { "starred": "$starred" },
"pipeline": [
{ "$match": {
"$expr": {
"$setIsSubset": [
"$$starred",
{ "$reduce": {
"input": "$faculties.subjects.id",
"initialValue": [],
"in": { "$concatArrays": [ "$$value", "$$this" ] }
}}
]
}
}},
{ "$project": {
"_id": 0,
"subjects": {
"$filter": {
"input": {
"$reduce": {
"input": "$faculties.subjects",
"initialValue": [],
"in": { "$concatArrays": [ "$$value", "$$this" ] }
}
},
"cond": { "$in": [ "$$this.id", "$$starred" ] }
}
}
}},
{ "$unwind": "$subjects" },
{ "$replaceRoot": { "newRoot": "$subjects" } }
],
"as": "subjects"
}}
])
这两种方法基本上都依赖于$reduce
和$concatArrays
,以便将“嵌套数组”内容“扁平化”为可用于比较的形式。两者之间的主要区别在于MongoDB 3.6之前你基本上是从文档中提取所有“可能的”匹配,然后你可以做任何关于将内部数组条目“过滤”到只有那些匹配的内容。
使用$reduce
和$in
运算符至少没有MongoDB 3.4,那么你实际上是在诉诸$unwind
:
db.users.aggregate([
{ "$match": { "name": "Michael" } },
{ "$lookup": {
"from": "schools",
"localField": "starred",
"foreignField": "faculties.subjects.id",
"as": "subjects"
}},
{ "$unwind": "$subjects" },
{ "$unwind": "$subjects.faculties" },
{ "$unwind": "$subjects.faculties.subjects" },
{ "$redact": {
"$cond": {
"if": {
"$setIsSubset": [
["$subjects.faculties.subjects.id"],
"$starred"
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}},
{ "$group": {
"_id": "$_id",
"id": { "$first": "$id" },
"name": { "$first": "$name" },
"starred": { "$first": "$starred" },
"subjects": { "$push": "$subjects.faculties.subjects" }
}}
])
当然使用$redact
阶段来过滤逻辑比较,因为只有$expr
与MongoDB 3.6和$setIsSubset
进行比较才能与"starred"
数组进行比较。
然后当然由于所有$unwind
操作,您通常需要$group
才能改造数组。
或者从另一个方向执行$lookup
:
db.schools.aggregate([
{ "$unwind": "$faculties" },
{ "$unwind": "$faculties.subjects" },
{ "$lookup": {
"from": "users",
"localField": "faculties.subjects.id",
"foreignField": "starred",
"as": "users"
}},
{ "$unwind": "$users" },
{ "$match": { "users.name": "Michael" } },
{ "$group": {
"_id": "$users._id",
"id": { "$first": "$users.id" },
"name": { "$first": "$users.name" },
"starred": { "$first": "$users.starred" },
"subjects": {
"$push": "$faculties.subjects"
}
}}
])
最后一种形式并不理想,因为在$lookup
完成之后(或技术上讲“在”$lookup
期间),您不会过滤“用户”。但无论如何,它首先需要与整个“学校”系列合作。
所有表单都返回相同的输出:
{
"_id" : ObjectId("5aea649526a94676bb981df4"),
"id" : 1,
"name" : "Michael",
"starred" : [
1,
2
],
"subjects" : [
{
"id" : 1,
"name" : "sub1"
},
{
"id" : 2,
"name" : "sub2"
}
]
}
您只有来自相关文档的"subjects"
内部数组的详细信息,该数组实际上与当前用户的"starred"
值匹配。
所有这些都表明,使用MongoDB“嵌套数组”并不是一个好主意。在MongoDB 3.6之前,您甚至无法执行atomic updates of "nested arrays",即使有允许它的更改,它仍然“困难”最好进行任何查询操作,尤其是那些涉及连接和过滤的操作。
构建“嵌套数组”是一个常见的“新手”错误,因为你似乎认为你正在“更好地”组织“事物”。但事实上,它更像是一种“反模式”,你真的应该考虑一种“更平坦”的结构,例如:
{
"_id" : ObjectId("5aea651326a94676bb981df5"),
"id" : 1,
"name" : "Uni",
"subjects" : [
{
"id" : 1,
"name" : "sub1",
"facultyId": 1000,
"facultyName": "faculty1"
},
{
"id" : 2,
"name" : "sub2",
"facultyId": 1000,
"facultyName": "faculty1"
},
{
"id" : 3,
"name" : "sub3",
"facultyId": 1000,
"facultyName": "faculty1"
}
]
}
“很多”更容易使用,当然还可以根据需要执行“加入”。