user1272965
user1272965

Reputation: 3064

Find subdocuments in mongo

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

Answers (2)

Wedge Martin
Wedge Martin

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

Koitoer
Koitoer

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

Related Questions