Stuart
Stuart

Reputation: 3356

MongoDB get SubDocument

I would like to retrieve a sub document from a document in MongoDB. I have the following document:

{
    "_id" : "10000",
    "password" : "password1",
    "name" : "customer1",
    "enabled" : true,
    "channels" : [ 
        {
            "id" : "10000-1",
            "name" : "cust1chan1",
            "enabled" : true
        }, 
        {
            "id" : "10000-2",
            "name" : "cust1chan2",
            "enabled" : true
        }
    ]
}

The result I would like is:

{
    "id" : "10000-1",
    "name" : "cust1chan1",
    "enabled" : true
}

However, the best I can do so far is using the following query:

db.customer.find({"channels.id" : "10000-1"}, {"channels.$" : 1, "_id" : 0})

But this gives me the following result:

{
    "channels" : [ 
        {
            "id" : "10000-1",
            "name" : "cust1chan1",
            "enabled" : true
        }
    ]
}

Does anyone know if it is possible to write a query that will give me my desired result? Any help would be much appreciated.

Upvotes: 21

Views: 24280

Answers (3)

MeVimalkumar
MeVimalkumar

Reputation: 3361

I know it may be a bit late, but I was in a similar situation and I figure out the solutions for this. Inside the aggregation pipeline, you can first filter out the data by matching sub-documents and then just unwind the nested array field and then replace the root.

db.customer.aggregate([
    [
        { $match: { "channels.id": "10000-1" }},
        { $unwind: "$channels" },
        { $replaceRoot: { newRoot: "$channels" } }
    ]
])

Upvotes: 0

chridam
chridam

Reputation: 103455

Using MongoDB 3.4.4 and newer, the aggregation framework offers a number of operators that you can use to return the desired subdocument.

Consider running an aggregate pipeline that uses a single $replaceRoot stage to promote the filtered subdocument to the top-level and replace all other fields.

Filtering the subdocument requires the $filter operator which selects a subset of an array to return based on the specified condition i.e. returns an array with only those elements that match the condition. You can then convert the single array element to a document by using the $arrayElemAt operator

Overall running this aggregate operation will yield the desired result:

db.customer.aggregate([
    { "$replaceRoot": { 
        "newRoot": {
            "$arrayElemAt": [
                { "$filter": {
                   "input": "$channels",
                   "as": "channel",
                   "cond": { /* resolve to a boolean value and determine if an element should be included in the output array. */
                       "$eq": ["$$channel.id", "10000-1"]
                    } 
                } },
                0 /* the element at the specified array index */
            ]
        }
    } }
])

Output

{
    "id" : "10000-1",
    "name" : "cust1chan1",
    "enabled" : true
}

Upvotes: 3

Parvin Gasimzade
Parvin Gasimzade

Reputation: 26032

You can do it with Aggregation Framework. Query will be something like :

db.customer.aggregate([
    {$unwind : "$channels"},
    {$match : {"channels.id" : "10000-1"}},
    {$project : {_id : 0, 
                 id : "$channels.id", 
                 name : "$channels.name", 
                 enabled : "$channels.enabled"}}
])

Upvotes: 14

Related Questions