naughty boy
naughty boy

Reputation: 2149

How to project specific fields from a document inside an array?

here is a typical document

{
   title : 'someTitle',
   places : [{name : 'someName', location : 'someLocation'}, {name ...}]
}

I have the following query

var qs = {title : 'someTitle', places : {$elemMatch : {name : 'someName' } } };

where I select a document which matches the title and which contains a document entry within its 'places' array that has name equal to 'someName'. However the issue is that the entries within the places array are large documents, and I only need a couple of fields from that document. I tried projecting the fields like so but it did not work.

var projection = {'places.$.name': 1, 'places.$.location' : 1};

Which is supposed to return an array with a document containing only the 'name' and 'location' property.

I got the following error

Can't canonicalize query: BadValue Cannot specify more than one positional proj. per query.  

to be clear, I would like to accomplish this without the aggregate framework

Upvotes: 22

Views: 27974

Answers (2)

Steve
Steve

Reputation: 1067

For the fields inside an array, you can project them the same as in embedded object
var projection = {'places.name': 1, 'places.location' : 1};
Check this guideline
https://docs.mongodb.com/manual/reference/operator/aggregation/project/#include-specific-fields-from-embedded-documents

Upvotes: 14

Sede
Sede

Reputation: 61293

You are doing it wrong. According to the documentation

Only one positional $ operator may appear in the projection document.

But you still need to use the $ operator to get the expected result:

var qs = { title : 'someTitle', 'places.name' : 'someName' };
var projection = {'places.$': 1 };
db.collection.find(qs, projection);

Which returns:

{
        "_id" : ObjectId("564f52d7d9a433df958b5630"),
        "places" : [
                {
                        "name" : "someName",
                        "location" : "someLocation"
                }
        ]
}

Also you don't need the $elemMatch operator here use "dot notation" instead.


Now if what you want is an array of "name" and "location" for each subdocument in the array then aggregation is the way to go.

db.collection.aggregate([
    { '$match': { 
        'title' : 'someTitle', 
        'places.name' : 'someName' 
    }}, 
    { '$project': { 
        'places': {
            '$map': { 
                'input': '$places', 
                'as': 'place', 
                'in': { 
                    'name': '$$place.name', 
                    'location': '$$place.location'
                }
            }
        }
    }}
])

Which yields:

{
    "_id" : ObjectId("564f52d7d9a433df958b5630"),
    "places" : [
            {
                    "name" : "someName",
                    "location" : "someLocation"
            },
            {
                    "name" : "bar",
                    "location" : "foo"
            }
    ]
}

Upvotes: 36

Related Questions