Reputation: 3064
I find a lot of questions about how to find a document based on some subdocument criterion, but is it possible to retrieve the subdocuments themselves, not the document in which they are contained?
Note - the proposed duplicate is not a duplicate. That OP asks how to return documents in a collection, each with a subset of matching subdocuments. My question is, how to retrieve only subdocuments that match.
From a collection like this:
{ name:"a" subs: [ { subname:"aa", value: 1 }, { subname:"ab", value: 2 } ] }
{ name:"b" subs: [ { subname:"ba", value: 2 }, { subname:"bb", value: 3 } ] }
I'd like to do a query that produces, just subdocs matching a query, say, where value === 2
.
I tried this:
myCollection.find().elemMatch("subs", { value:2 })
And that's close, but it just finds (in the example data), both upper-level documents, since their sub elements match. I know I could pick out the subdocuments from the result, but I'd like the query to do the work, and produce this...
{ subname:"ab", value: 2 }
{ subname:"ba", value: 2 }
... i.e. just the subdocs that match. Is it possible? Thanks in advance.
Upvotes: 0
Views: 1295
Reputation: 825
You can specify what to return in the second argument of the find. Try this with your data:
db.myCollection.find( { subs: { $elemMatch: { "value" : 2 } } }, { "subs.$": 1 } );
Also, some good examples here:
https://docs.mongodb.org/v3.0/reference/operator/projection/positional/#proj.S
Upvotes: 1
Reputation: 19533
One option could be use aggregation fwk, below are my example
db.foo.insert({ name:"a", subs : [ { subname:"aa", value: 1 }, { subname:"ab", value: 2 } ] })
db.foo.insert({ name:"b" , subs: [ { subname:"ba", value: 2 }, { subname:"bb", value: 3 } ] })
db.foo.insert({ name:"c", subs : [ { subname:"aa", value: 1 }, { subname:"ab", value: 2 }, { subname:"acc", value: 2 } ] })
Using
db.foo.aggregate(
[
{ $unwind : "$subs" },
{ $match : { "subs.value": 2 }},
{ $project : {"subs" : 1 , "_id" : 0} }
]
)
Result is
{ "subs" : { "subname" : "ab", "value" : 2 } }
{ "subs" : { "subname" : "ba", "value" : 2 } }
{ "subs" : { "subname" : "ab", "value" : 2 } }
{ "subs" : { "subname" : "acc", "value" : 2 } }
If you want to remove duplicate do a final $group
db.foo.aggregate(
[
{ $unwind : "$subs" },
{ $match : { "subs.value": 2 }},
{ $group : { "_id": { "subname" : "$subs.subname" , "value" : "$subs.value" }}},
{ $project : { "subs" : "$_id" , "_id" : 0}}
]
)
Result will be
{ "subs" : { "subname" : "acc", "value" : 2 } }
{ "subs" : { "subname" : "ba", "value" : 2 } }
{ "subs" : { "subname" : "ab", "value" : 2 } }
Upvotes: 1