David Dev
David Dev

Reputation: 53

How to filter array of sub-documents?

I have a document structured like this:

{ 
"_id" : ObjectId("564d2702d4c68225cb00726f"), 
"list" : [
    {
        "a" : NumberInt(1), 
        "test" : "public"
    }, 
    {
        "a" : NumberInt(2), 
        "test" : "public"
    }, 
    {
        "a" : NumberInt(3), 
        "test" : "public"
    }, 
    {
        "a" : NumberInt(4), 
        "test" : "public"
    }, 
    {
        "a" : NumberInt(5), 
        "test" : "public"
    }
],
"other_fields": ""}

Can I filter subdocument for a in (1, 5)

My expect result below

{
"_id" : ObjectId("564d2702d4c68225cb00726f"),
"list" : [
    {
        "a" : NumberInt(1),
        "test" : "public"
    },
    {
        "a" : NumberInt(5),
        "test" : "public"
    }
]}

I try to use $elemMatch, but when I use $in, an error happened.

Upvotes: 4

Views: 967

Answers (2)

Sede
Sede

Reputation: 61225

Starting from MongoDB 3.2, we can use the $filter operator to efficiently to this. In $filter's conditional expression we need to use the $setIsSubset operator to check if a given value is in the array. This is mainly because we can't use the $in query operator in the the $project stage.

db.collection.aggregate([
    { "$project": { 
        "list": { 
            "$filter": { 
                "input": "$list", 
                "as": "lst", 
                "cond": { "$setIsSubset": [ [ "$$lst.a" ], [ 1, 5 ] ] }
            } 
        } 
    }}
])

From MongoDB 3.0.x backwards you need a different, less efficient approach using the $map operator the and the $setDifference operator.

db.collection.aggregate([
    { "$project": { 
        "list": { 
            "$setDifference": [ 
                { "$map": { 
                    "input": "$list", 
                    "as": "lst", 
                    "in": { 
                        "$cond": [
                            { "$setIsSubset": [ [ "$$lst.a" ], [ 1, 5 ] ] },
                            "$$lst",
                            false
                        ] 
                    } 
                }}, 
                [false] 
            ]
        }
    }}
])

Upvotes: 3

Shrabanee
Shrabanee

Reputation: 2766

Try following query:-

db.collection.aggregate([
{ 
    "$match": {
       $or[{ "list.a": NumberInt(1)}, {{ "list.a": NumberInt(5)}}]
    }
},
{
    "$project": {
        "list": {
            "$setDifference": [
                {
                    "$map": {
                        "input": "$list",
                        "as": "o",
                        "in": {
                            "$cond": [
                                {  
                                     $or[{ "list.a": NumberInt(1)}, 
                                         {{ "list.a": NumberInt(5)}}]
                                },
                                "$$o",
                                false
                            ]
                        }
                    }
                },
                [false]
            ]
        }
    }
}
])

By using the query result will be:-

{
    "_id" : ObjectId("564d2702d4c68225cb00726f"),
    "list" : [
    {
        "a" : NumberInt(1),
        "test" : "public"
    },
    {
        "a" : NumberInt(5),
        "test" : "public"
    }
]}

Upvotes: 0

Related Questions