Alex
Alex

Reputation: 123

Project only some fields of array items in sub document

How can I project only particular fields of items in array in sub document?

Consider the following (simplified) example:

{
    "_id" : ObjectId("573d70df080cc2cbe8bf3222"),
    "name" : "Nissan",
    "models" : [
        {
            "name" : "Altima",
            "body" : {
                 "type" : 2,
                 "maxprice" : 31800.00,
                 "minprice" : 21500.00
            }
        },
        {
             "name" : "Maxima",
             "body" : {
                 "type" : 2,
                 "maxprice" : 39200.00,
                 "minprice" : 28800.00
             }
        }
    ]
},
{
    "_id" : ObjectId("80cc2cbe8bf3222573d70df0"),
    "name" : "Honda",
    "models" : [
        {
            "name" : "Accord",
            "body" : {
                "type" : 2,
                "maxprice" : 34100.00,
                "minprice" : 20400.00
            }
        },
        {
            "name" : "Civic",
            "body" : {
                "type" : 3,
                "maxprice" : 27900.00,
                "minprice" : 19800.00
             }
        }
    ]
}

After aggregation, I'd like to get the following output:

{
    "_id" : ObjectId("573d70df080cc2cbe8bf3222"),
    "name" : "Nissan",
    "models" : [
        {
            "type" : 2,
            "minprice" : 21500.00
        },
        {
            "type" : 2,
            "minprice" : 28800.00
        }
    ]
},
{
    "_id" : ObjectId("80cc2cbe8bf3222573d70df0"),
    "name" : "Honda",
    "models" : [
        {
            "type" : 2,
            "minprice" : 20400.00
        },
        {
            "type" : 3,
            "minprice" : 19800.00
        }
    ]
}

So it basically gets all documents, all fields of documents, all items in models array, BUT only some fields of the array items in models. Please help.

Upvotes: 10

Views: 6845

Answers (2)

Sede
Sede

Reputation: 61263

You need to $project the "models" field using the $map operator.

db.collection.aggregate([ 
    { "$project": { 
        "name": 1, 
        "models": { 
            "$map": { 
                "input": "$models", 
                "as": "m", 
                "in": { 
                    "type": "$$m.body.type", 
                    "minprice": "$$m.body.minprice" 
                } 
            } 
        }  
    }} 
])

Upvotes: 16

grodzi
grodzi

Reputation: 5703

$unwind is your friend

First you can basically filter the (non nested) fields you want.

var projection = {$project:{name:'$name', models:'$models'}};
db.dum.aggregate(projection)

Foreach of your models, you issue a document

var unwindModels = {$unwind:{'$models'}}
db.dum.aggregate(projection, unwindModels)

The idea is that every document issued from your models field will be regrouped later on via the _id field.

Foreach document, you only keep the (sub)fields you want

var keepSubFields = {$project:{name:'$name', type:'$models.body.type', minprice:'$models.body.minprice'}}
db.dum.aggregate(projection, unwindModels, keepSubFields)

Then you reaggregate your models as an array (thanks to the _id of each record which tracks the original record)

var aggregateModels = {$group:{_id:'$_id', name:{$last:'$name'}, models:{$push:{type:'$type', minprice:'$minprice'}}}}
db.dum.aggregate(projection, unwindModels, keepSubFields, aggregateModels)

note1: Here we can use $last because our primary key is not _id but <_id, name>. ($first would be good too)

note2: we refer type by $type, because when you iterate the collection on the aggregateModels stage, your record is of the form <_id, name, type, minprice>

Upvotes: -1

Related Questions