Simo L.
Simo L.

Reputation: 321

Is there a Mongo function for filtering all nested/subdocuments based on a field?

I have some documents in MongoDB that have other nested documents all with an "active" field. For instance :

{
    "id": "PRODUCT1",
    "name": "Product 1",
    "active": true,
    "categories": [
        {
            "id": "CAT-1",
            "active": true,
            "subcategories": [
                {
                    "id": "SUBCAT-1",
                    "active": false
                },
                {
                    "id": "SUBCAT-2",
                    "active": true
                }
            ]
        },
        {
            "id": "CAT-2",
            "active": false,
            "subcategories": [
                {
                    "id": "SUBCAT-3",
                    "active": true
                }
            ]
        }
    ]
}

Is there a way to find all documents but only keep the "active" nested documents.

This is the result I'd like :

{
    "id": "PRODUCT1",
    "name": "Product 1",
    "active": true,
    "categories": [
        {
            "id": "CAT-1",
            "active": true,
            "subcategories": [
                {
                    "id": "SUBCAT-2",
                    "active": true
                }
            ]
        }
    ]
}

Knowing that I do NOT know the document schema beforehand. That's why I need a sort of conditioned wildcard projection... (ie *.active=true). Is this possible or this HAS to be done serverside ?

Upvotes: 4

Views: 277

Answers (3)

s7vr
s7vr

Reputation: 75964

Use $redact.

db.collection.aggregate(
   [
     { $redact: {
        $cond: {
           if: { $eq:["$active", true] },
           then: "$$DESCEND",
           else: "$$PRUNE"
         }
       }
     }
   ]
);

https://mongoplayground.net/p/7UMphkH5OWn

Upvotes: 7

Kevin Smith
Kevin Smith

Reputation: 14456

You'll be able to achieve this with a few $map, $reduce and $filter stages.

db.collection.aggregate([
  {
    "$addFields": {
      "categories": {
        "$filter": {
          "input": "$categories",
          "cond": {
            $eq: [
              "$$this.active",
              true
            ]
          }
        }
      }
    }
  },
  {
    "$addFields": {
      "categories": {
        "$map": {
          "input": "$categories",
          "in": {
            "$mergeObjects": [
              "$$this",
              {
                "subcategories": {
                  "$filter": {
                    "input": "$$this.subcategories",
                    "cond": {
                      $eq: [
                        "$$this.active",
                        true
                      ]
                    }
                  }
                }
              }
            ]
          }
        }
      }
    }
  }
])

Executing the above will give you the following result based on your input

[
  {
    "_id": ObjectId("5a934e000102030405000000"),
    "active": true,
    "categories": [
      {
        "active": true,
        "id": "CAT-1",
        "subcategories": [
          {
            "active": true,
            "id": "SUBCAT-2"
          }
        ]
      }
    ],
    "id": "PRODUCT1",
    "name": "Product 1"
  }
]

https://mongoplayground.net/p/fkkby-eibx2

Upvotes: 0

Mallik
Mallik

Reputation: 334

//actual code out from mongo shell 4.2 on windows
//sample document as shared in problem statement, query to find the document from //collection
> db.products.find().pretty();
{
        "_id" : ObjectId("5f748ee5377e73757bb7ceac"),
        "id" : "PRODUCT1",
        "name" : "Product 1",
        "active" : true,
        "categories" : [
                {
                        "id" : "CAT-1",
                        "active" : true,
                        "subcategories" : [
                                {
                                        "id" : "SUBCAT-1",
                                        "active" : false
                                },
                                {
                                        "id" : "SUBCAT-2",
                                        "active" : true
                                }
                        ]
                },
                {
                        "id" : "CAT-2",
                        "active" : false,
                        "subcategories" : [
                                {
                                        "id" : "SUBCAT-3",
                                        "active" : true
                                }
                        ]
                }
        ]
}
//verify mongo shell version no. for reference
> db.version();
4.2.6
//using aggregate and $unwind you can query the inner array elements as shown below
> db.products.aggregate([
... {$unwind: "$categories"},
... {$unwind: "$categories.subcategories"},
... {$match:{"active":true,
...          "categories.active":true,
...          "categories.subcategories.active":true}}
... ]).pretty();
{
        "_id" : ObjectId("5f748ee5377e73757bb7ceac"),
        "id" : "PRODUCT1",
        "name" : "Product 1",
        "active" : true,
        "categories" : {
                "id" : "CAT-1",
                "active" : true,
                "subcategories" : {
                        "id" : "SUBCAT-2",
                        "active" : true
                }
        }
}
>

Upvotes: 1

Related Questions