您的架构正在使用对另一个集合中对象的引用。由于 MongoDB 本身实际上一次只查询一个集合,因此您正在查询的集合中不存在“引用”数据。
该集合所知道的只是它有一个包含 ObjectId 值的“数组”字段,这实际上对 MongoDB 本身没有任何意义,唯一知道 ObjectId 值实际引用的位置是在您的“客户端代码”中的猫鼬模式。所以服务器当然对此一无所知。
使用 MongoDB
在 MongoDB 的现代版本(至少 3.2)中,您可以使用 $lookup 聚合管道运算符在服务器上执行“加入”(或者更确切地说是“查找”)。此语法允许您为在数组中找到的值定义要“查找”的集合,结果将从引用的集合中检索匹配的对象到您的文档中。
数组的问题在于,根据您的版本,可能需要使用$unwind 处理才能进行匹配。 MongoDB 3.2 要求数组是“未缠绕的”,但在 MongoDB 3.4 中你应该能够做到这一点:
db.getCollection('classrooms').aggregate([
{ "$lookup": {
"from": "robotclassrooms",
"localField": "robots",
"foreignField": "_id"
"as": "robots"
}},
{ "$redact": {
"$cond": {
"if": {
"$anyElementTrue": {
"$map": {
"input": "$robots",
"as": "r",
"in": { "$eq": [ "$$r.robot", ObjectId("59226c258a4a131c6828841e") ] }
}
}
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
并使用$unwind:
db.getCollection('classrooms').aggregate([
{ "$unwind": "$robots" },
{ "$lookup": {
"from": "robotclassrooms",
"localField": "robots",
"foreignField": "_id"
"as": "robots"
}},
{ "$unwind": "$robots" },
{ "$group": {
"_id": "_id",
"updatedAt": { "$first": "$updatedAt" },
"createdAt": { "$first": "$createdAt" },
"code": { "$first": "$code" },
"createdBy": { "$first": "$createdBy" },
"robots": { "$push": "$robots" }
}},
{ "$redact": {
"$cond": {
"if": {
"$anyElementTrue": {
"$map": {
"input": "$robots",
"as": "r",
"in": { "$eq": [ "$$r.robot", ObjectId("59226c258a4a131c6828841e") ] }
}
}
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
注意$unwind 在这种情况下的双重用法,因为 MongoDB 3.2 不仅要求在处理之前解开数组,而且结果也是一个“数组”,即使它可能只匹配一个值。 $lookup 运算符不区分单个匹配和多个匹配,因为 always 返回一个数组。在任何版本中。
因此还使用$group 进行后续处理以恢复文档结构。我不会在此处复制您的所有文档属性,而是复制其中的足够多的内容,以便您了解一般的“要点”。
使用猫鼬
实际要求“服务器”执行此操作的替代过程是使用“mongoose”将代码变白以执行“填充查询”。这是“连接”的“客户端模拟”,其中实际发生的是向数据库发出的多个请求,结果在代码中“合并”。
当然,作为“客户端”操作,这有一些限制。最值得注意的是,服务器将返回所有结果,无论它们是否符合您的条件,并且条件在“客户端代码”中进行评估。
Classroom.find()
.populate({
path: 'robots',
match: { 'robot': '59226c258a4a131c6828841e' }
}).exec(function(err,docs) {
// docs has all classrooms but some robots arrays will be empty
});
所以这里的问题是总是返回evert“classrooms”文档,即使条件是只从“robotsclassrooms”中“填充”那些与给定查询条件匹配的对象。
结果是“教室”对象具有未满足查询的“空数组”。然后,您需要解决这个问题,但是“过滤”生成的课堂文档以查找空数组,如下所示:
Classroom.find()
.populate({
path: 'robots',
match: { 'robot': '59226c258a4a131c6828841e' }
}).exec(function(err,docs) {
docs = docs.filter(function(doc) { return doc.robots.length > 0 })
// Now docs is only matching documents containing the requested ObjectId value
});
结论
这些基本上是您的选择,您使用哪一种取决于您的方法,当然还有可用的服务器功能。
请注意,这些过程是“查找与数组中的元素匹配的文档”,这与“仅返回数组的匹配元素”不同。有不同的技术来“建立”这里的过程,但基本上也涉及“过滤”数组内容。例如,在聚合框架中,您可以应用 $filter 作为运算符来执行该功能。
注意事项:
有关示例中使用的其他运算符,另请参阅 Aggregation Operators 的完整列表。