MongoDB: How to find out if an array field contains an element?
I have two collections. The first collection contains students:
{ "_id" : ObjectId("51780f796ec4051a536015cf"), "name" : "John" }
{ "_id" : ObjectId("51780f796ec4051a536015d0"), "name" : "Sam" }
{ "_id" : ObjectId("51780f796ec4051a536015d1"), "name" : "Chris" }
{ "_id" : ObjectId("51780f796ec4051a536015d2"), "name" : "Joe" }
The second collection contains courses:
{
"_id" : ObjectId("51780fb5c9c41825e3e21fc4"),
"name" : "CS 101",
"students" : [
ObjectId("51780f796ec4051a536015cf"),
ObjectId("51780f796ec4051a536015d0"),
ObjectId("51780f796ec4051a536015d2")
]
}
{
"_id" : ObjectId("51780fb5c9c41825e3e21fc5"),
"name" : "Literature",
"students" : [
ObjectId("51780f796ec4051a536015d0"),
ObjectId("51780f796ec4051a536015d0"),
ObjectId("51780f796ec4051a536015d2")
]
}
{
"_id" : ObjectId("51780fb5c9c41825e3e21fc6"),
"name" : "Physics",
"students" : [
ObjectId("51780f796ec4051a536015cf"),
ObjectId("51780f796ec4051a536015d0")
]
}
Each course document contains students
array which has a list of students registered for the course. When a student views a course on a web page he needs to see if he has already registered for the course or not. In order to do that, when the courses
collection gets queried on the student's behalf, we need to find out if students
array already contains the student's ObjectId. Is there a way to specify in the projection of a find query to retrieve student ObjectId
from students
array only if it is there?
I tried to see if I could $elemMatch operator but it is geared towards an array of sub-documents. I understand that I could use aggregation framework but it seems that it would be on overkill in this case. Aggregation framework would probably not be as fast as a single find query. Is there a way to query course collection to so that the returned document could be in a form similar to this?
{
"_id" : ObjectId("51780fb5c9c41825e3e21fc4"),
"name" : "CS 101",
"students" : [
ObjectId("51780f796ec4051a536015d0"),
]
}
[edit based on this now being possible in recent versions]
[Updated Answer] You can query the following way to get back the name of class and the student id only if they are already enrolled.
db.student.find({},
{_id:0, name:1, students:{$elemMatch:{$eq:ObjectId("51780f796ec4051a536015cf")}}})
and you will get back what you expected:
{ "name" : "CS 101", "students" : [ ObjectId("51780f796ec4051a536015cf") ] }
{ "name" : "Literature" }
{ "name" : "Physics", "students" : [ ObjectId("51780f796ec4051a536015cf") ] }
[Original Answer] It's not possible to do what you want to do currently. This is unfortunate because you would be able to do this if the student was stored in the array as an object. In fact, I'm a little surprised you are using just ObjectId() as that will always require you to look up the students if you want to display a list of students enrolled in a particular course (look up list of Id's first then look up names in the students collection - two queries instead of one!)
If you were storing (as an example) an Id and name in the course array like this:
{
"_id" : ObjectId("51780fb5c9c41825e3e21fc6"),
"name" : "Physics",
"students" : [
{id: ObjectId("51780f796ec4051a536015cf"), name: "John"},
{id: ObjectId("51780f796ec4051a536015d0"), name: "Sam"}
]
}
Your query then would simply be:
db.course.find( { },
{ students :
{ $elemMatch :
{ id : ObjectId("51780f796ec4051a536015d0"),
name : "Sam"
}
}
}
);
If that student was only enrolled in CS 101 you'd get back:
{ "name" : "Literature" }
{ "name" : "Physics" }
{
"name" : "CS 101",
"students" : [
{
"id" : ObjectId("51780f796ec4051a536015cf"),
"name" : "John"
}
]
}
It seems like the $in
operator would serve your purposes just fine.
You could do something like this (pseudo-query):
if (db.courses.find({"students" : {"$in" : [studentId]}, "course" : courseId }).count() > 0) {
// student is enrolled in class
}
Alternatively, you could remove the "course" : courseId
clause and get back a set of all classes the student is enrolled in.