在我的Mongoose中,我有以下架构..
const RobotSchema = new Schema({
name: { type: String },
});
const RobotClassRoomSchema = new Schema({
robot: { type: Schema.Types.ObjectId, ref: 'Robot' },
});
const ClassRoomSchema = new Schema({
robots: [{ type: Schema.Types.ObjectId, ref: 'RobotClassRoom' }],
});
如何验证我的Robot
中是否有59226c258a4a131c6828841e
个ClassRoom
?
但我有机器人......
答案 0 :(得分:2)
您的架构正在使用对另一个集合中的对象的引用。由于MongoDB本身实际上只是一次查询一个集合,因此引用了#34;您正在查询的集合中不存在数据。
所有该集合都知道它有一个"数组"包含ObjectId
值的字段,实际上对MongoDB本身没有任何意义,唯一知道ObjectId
值实际引用的位置的东西是在你的mongoose模式中定义的客户端代码" 。所以服务器当然不知道这个。
在MongoDB的现代版本(至少3.2版)中,您可以使用$lookup
聚合管道运算符来执行" join" (或者说是#34;查找")在服务器上。此语法允许您定义您想要的集合"查看"对于在数组中找到的值,结果将从引用的集合中将匹配的对象检索到您的文档中。
数组的问题在于,根据您的版本,可能需要使用$unwind
进行处理才能进行匹配。 MongoDB 3.2要求数组是" un-wound",但是在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
运算符不区分单数和多重匹配,因为始终返回数组。在任何版本中。
因此,使用$group
进行后续处理以恢复文档结构。我没有在这里复制所有你的文档属性,但只是足够了,所以你得到了一般的" gist"。
实际询问"服务器"的替代流程要做这个动作就是使用" mongoose"来使代码变白。做一个"填充查询"。这是一个"客户端仿真" "加入",其中实际发生的事情是对数据库的多个请求,其结果为"合并"在代码中。
当然,作为"客户端"操作对此有一些限制。最值得注意的是,服务器将返回所有结果,无论它们是否符合您的条件,并且在"客户端代码"中评估条件。
Classroom.find()
.populate({
path: 'robots',
match: { 'robot': '59226c258a4a131c6828841e' }
}).exec(function(err,docs) {
// docs has all classrooms but some robots arrays will be empty
});
所以这里的问题是外翻"教室"始终返回文档,即使条件仅为"填充"那些来自" 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
});
这些基本上是您的选择,您使用哪一个取决于您的方法,当然还有可用的服务器功能。
请注意,这些过程是"查找与数组中的元素匹配的文档",这是与#34;不同的语句;只返回数组的匹配元素"。有不同的技术可以建立在"这里的过程,但基本上涉及"过滤"数组内容也是如此。例如,在聚合框架中,您可以应用$filter
作为运算符来执行该功能。
备注:强>
另请参阅示例中使用的其他运算符的Aggregation Operators的完整列表。