27

I will use the example from here

{
 _id: 1,
 zipcode: 63109,
 students: [
              { name: "john", school: 102, age: 10 },
              { name: "jess", school: 102, age: 11 },
              { name: "jeff", school: 108, age: 15 }
           ]
}
{
 _id: 2,
 zipcode: 63110,
 students: [
              { name: "ajax", school: 100, age: 7 },
              { name: "achilles", school: 100, age: 8 },
           ]
}

{
 _id: 3,
 zipcode: 63109,
 students: [
              { name: "ajax", school: 100, age: 7 },
              { name: "achilles", school: 100, age: 8 },
           ]
}

{
 _id: 4,
 zipcode: 63109,
 students: [
              { name: "barney", school: 102, age: 7 },
           ]
}

If I run

db.schools.find( { zipcode: 63109 },
             { students: { $elemMatch: { school: 102 } } } )

It will give the first result of each array. Naming this:

{ "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" : 10 } ] }
{ "_id" : 3 }
{ "_id" : 4, "students" : [ { "name" : "barney", "school" : 102, "age" : 7 } ] }

How can I make it return all the object of the array (and not only the first) that match the criteria? Meaning this:

{
 _id: 1,
 students: [
              { name: "john", school: 102, age: 10 },
              { name: "jess", school: 102, age: 11 }
           ]
}    
{ _id: 3 }
{_id: 4, students: [ { name: "barney", school: 102, age: 7 }]}
Diolor
  • 13,181
  • 30
  • 111
  • 179
  • looks like the documentation said that its $elemMatch only return first array element, so I guess you have to do it in python, or make new table with list of students + zipcode like `{ "name" : "john", "school" : 102, "age" : 10, zipcode: 63109 }` – YOU Oct 31 '13 at 02:23

3 Answers3

23

In order to return multiple subdocuments, you're going to need to use the aggregation framework. This will return all of the subdocuments you're looking for:

db.zip.aggregate(
  {$match: {zipcode: 63109}},
  {$unwind: "$students"},
  {$match: {"students.school": 102}}
)

You can do various things to get different output, but this will return:

{
    "result" : [
        {
            "_id" : 1,
            "zipcode" : 63109,
            "students" : {
                "name" : "john",
                "school" : 102,
                "age" : 10
            }
        },
        {
            "_id" : 1,
            "zipcode" : 63109,
            "students" : {
                "name" : "jess",
                "school" : 102,
                "age" : 11
            }
        },
        {
            "_id" : 4,
            "zipcode" : 63109,
            "students" : {
                "name" : "barney",
                "school" : 102,
                "age" : 7
            }
        }
    ],
    "ok" : 1
}
EmptyArsenal
  • 7,314
  • 4
  • 33
  • 56
2

Previous and incorrect answer:

This should work as of today. See https://docs.mongodb.com/v3.2/reference/operator/projection/positional/#array-field-limitations

You should get the correct result when querying using $elemMatch in the query and exposing the sub-document in the projection like following:

db.schools.find( { zipcode: 63109, students: { $elemMatch: { school: 102 } } },
                 { 'students.$': 1 } )


New answer

Limiting the list of sub-documents to those matching the query is as of now not possible using find(). Please take aggregate() instead or take one of the following possibilities:

You could either get all the sub-documents of the matching document by adding the array-property in the projection:

db.schools.find( { zipcode: 63109, students: { $elemMatch: { school: 102 } } }, { 'students': 1 })
> { "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" : 10 }, { "name" : "jess", "school" : 102, "age" : 11 }, { "name" : "jeff", "school" : 108, "age" : 15 } ] }
> { "_id" : 4, "students" : [ { "name" : "barney", "school" : 102, "age" : 7 } ] }

Or you can get the first item matching the $elemMatch query on the sub-documents:

db.schools.find( { zipcode: 63109, students: { $elemMatch: { school: 102 } } }, { 'students.$': 1 })
> { "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" : 10 } ] }
> { "_id" : 4, "students" : [ { "name" : "barney", "school" : 102, "age" : 7 } ] }
SimonSimCity
  • 6,415
  • 3
  • 39
  • 52
  • Because your projection example as per the documentation returns the first match, not all which was the question. – Alf Sep 14 '18 at 18:10
  • Tried it a few times and updated the answer to something valid :) @alf Not directly what was asked for, but maybe helpful for others. – SimonSimCity Sep 16 '18 at 16:18
0

This worked for me in a case of similar filtering. Now I know this question was asked many years ago, but to anyone looking for an answer like me. here's what worked for me. Thanks to original answer!

In the case of this particular question: outerparam is zipcode and innerarray.property is students.school.

let cursor = db
    .collection("somecollection")
    .aggregate(
      { $match: { outerparam: outermatch } },
      { $unwind: "$innerarray" },
      { $match: { "innerarray.property": propertymatch } },
      { $project: { "innerarray.$": 1 } });