In a new version on MongoDB we can use an $elemMatch projection operator to limit the response of a query to a single matching element of an array. http://docs.mongodb.org/manual/reference/projection/elemMatch/
But it seems doesn't work yet in mongoose 3 here is the example:
{
_id: ObjectId(5),
items: [1,2,3,45,4,67,9,4]
}
Folder.findOne({_id: Object(5)}, {$elemMatch: {$in: [1,67,9]}})
.exec(function (err, doc) {
});
I'm expected to get the follows doc:
{
_id: ObjectId(5),
items: [1,67,9]
}
But unfortunately what I'm getting is document with all items:
{
_id: ObjectId(5),
items: [1,2,3,45,4,67,9,4]
}
The mongodb docs here are misleading, we'll get them updated.
What its saying is that you can now use $elemMatch in your projection, that is, your field selection:
https://gist.github.com/3640687
See also: https://github.com/learnboost/mongoose/issues/1085
[Edit] pull request for docs sent: https://github.com/mongodb/docs/pull/185
Firstly, you are missing the items
field name in front of the $elemMatch operator. Your query should read
Folder.findOne({_id: Object(5)}, {items: {$elemMatch: {$in: [1,67,9]}}})
.exec(function (err, doc) { });
But this would still not return the desired result, because as stated in the documentation:
The $elemMatch projection will only match one array element per source document.
So you would only get back something like:
{
_id: ObjectId(5),
items: [1]
}
I haven't got mongoose set up to do this with node, but you can also get the result you want using the new aggregation framework in 2.2 - here's an example that gets you the result you wanted. First, my sample doc looks like this:
> db.foo.findOne()
{
"_id" : ObjectId("50472eb566caf6af6108de02"),
"items" : [
1,
2,
3,
45,
4,
67,
9,
4
]
}
To get to what you want I did this:
> db.foo.aggregate(
{$match : {"_id": ObjectId("50472eb566caf6af6108de02")}},
{$unwind : "$items"},
{$match : {"items": {$in : [1, 67, 9]}}},
{$group : {_id : "$_id", items : { $push : "$items"}}},
{$project : {_id : 0, items : 1}}
)
{
"result" : [
{
"_id" : ObjectId("50472eb566caf6af6108de02"),
"items" : [
1,
67,
9
]
}
],
"ok" : 1
}
To explain, in detail I will take it line by line:
{$match : {"_id": ObjectId("50472eb566caf6af6108de02")}}
This is fairly obvious - it is basically the equivalent to the find criteria on a regular query, the results are passed to the next step in the pipeline to be processed. This is the piece that can use indexes etc.
{$unwind : "$items"}
This will explode the array, creating a stream of documents, one for each element of the array.
{$match : {"items": {$in : [1, 67, 9]}}}
This second match will return only the documents in the list, basically reducing the stream of docs to a result set of three.
{$group : {_id : "$_id", items : { $push : "$items"}}}
We want our output to be an array, so we have to undo the unwind above now that we have selected the items we want, using the _id as the key to group. Note: this will have repeating values if there is more than one match, if you wanted a unique list you would use $addToSet
instead of $push
{$project : {_id : 1, items : 1}}
Then finally, this projection is not really needed, but I included it to illustrate the functionality - you could choose to not return the _id if you wished etc.
Neither $elemMatch nor MongoDB in general will filter data from an array. $elemMatch can be used to match a document but it won't affect the data to be returned. You can only include/exclude fields from a documented by using the filter parameter (second parameter of a find() findOne() call) but you can not filter the result based on some query input.