sfarzoso
sfarzoso

Reputation: 1610

How to find records in nested documents?

So I'm learning mongoose and I've implemented a Customer model like this:

let CustomerSchema = new Schema({
    stripe_id: {
        type: String,
        required: true
    },
    telegram_id: {
        type: Number
    },
    email: {
        type: String,
        required: true
    },
    subscriptions: [SubscriptionSchema],
    created_at: {
        type: Date,
        default: Date.now,
        required: true
    }
});

essentially I would like to return all the subscriptions of a customer, but how can I search in nested document, in this case subscriptions?

This is the subscription model:

let SubscriptionSchema = new Schema({
    status: {
        type: String,
        required: true
    },
    plan_id: {
        type: String,
        required: true
    }
});

I would like to return only the subscriptions which have as status active, at the moment I'm able to search for customer as:

let customer = await CustomerModel.findOne({telegram_id: ctx.chat.id});

Upvotes: 1

Views: 63

Answers (2)

SuleymanSah
SuleymanSah

Reputation: 17888

You can use the filter aggregation to filter in a nested array.

Playground

Sample express route with mongoose:

router.get("/customers/:id", async (req, res) => {

  let result = await Customer.aggregate([
    {
      $match: {
        telegram_id: 1 //todo: req.params.id
      }
    },
    {
      $project: {
        _id: "$_id",
        stripe_id: "$stripe_id",
        telegram_id: "$telegram_id",
        email: "$email",
        subscriptions: {
          $filter: {
            input: "$subscriptions",
            as: "item",
            cond: {
              $eq: ["$$item.status", "active"]
            }
          }
        }
      }
    }
  ]);

 //todo: result will be an array, you can return the result[0] if you want to return as object
  res.send(result); 
});

Let'a say we have the following document:

{
    "_id" : ObjectId("5e09eaa1c22a8850c01dff77"),
    "stripe_id" : "stripe_id 1",
    "telegram_id" : 1,
    "email" : "[email protected]",
    "subscriptions" : [
        {
            "_id" : ObjectId("5e09eaa1c22a8850c01dff7a"),
            "status" : "active",
            "plan_id" : "plan 1"
        },
        {
            "_id" : ObjectId("5e09eaa1c22a8850c01dff79"),
            "status" : "passive",
            "plan_id" : "plan 2"
        },
        {
            "_id" : ObjectId("5e09eaa1c22a8850c01dff78"),
            "status" : "active",
            "plan_id" : "plan 3"
        }
    ],
    "created_at" : ISODate("2019-12-30T15:16:33.967+03:00"),
    "__v" : 0
}

The result will be like this:

[
    {
        "_id": "5e09eaa1c22a8850c01dff77",
        "stripe_id": "stripe_id 1",
        "telegram_id": 1,
        "email": "[email protected]",
        "subscriptions": [
            {
                "_id": "5e09eaa1c22a8850c01dff7a",
                "status": "active",
                "plan_id": "plan 1"
            },
            {
                "_id": "5e09eaa1c22a8850c01dff78",
                "status": "active",
                "plan_id": "plan 3"
            }
        ]
    }
]

If you don't want to project the items one by one, you can use addFields aggregation like this:

router.get("/customers/:id", async (req, res) => {
  let result = await Customer.aggregate([
    {
      $match: {
        telegram_id: 1
      }
    },
    {
      $addFields: {
        subscriptions: {
          $filter: {
            input: "$subscriptions",
            as: "item",
            cond: {
              $eq: ["$$item.status", "active"]
            }
          }
        }
      }
    }
  ]);

  res.send(result);
});

Upvotes: 1

Rupesh
Rupesh

Reputation: 890

You can do something like this.

await CustomerModel.findOne({telegram_id: ctx.chat.id})
      .populate({
         path: 'subscriptions',
         match: {status: 'active'}
 });

here path is used to join the next model and match is used to query inside that model.

Upvotes: 1

Related Questions