Search code examples
mongodbmongoosenested-documents

MongoDB/Mongoose how to return a nested subdocument by _id


MongoDB newbie here.

I have a 'client' document that looks like this:

{
  name: "myClient",
  products: [{
    name: "myProduct1",
    environments: [{
        name: "myEnvironment1",
        changeLogs: [
          { "some": "fields21" },
          { "some": "fields22" }
        ]
      },
      {
        name: "myEnvironment2",
        changeLogs: [
          { "some": "fields11" },
          { "some": "fields12" }
        ]
      }
    ]
  },
  {
    name: "myProduct2",
    environments: [{
        name: "myEnv1",
        changeLogs: [
          { "some": "fields1" },
          { "some": "fields2" }
        ]
      },
      {
        name: "myEnv1",
        changeLogs: [
          { "some": "fields3" },
          { "some": "fields4" }
        ]
      }
    ]
  }]
}

So a client has many products, which has many environments, which has many changeLogs. I am looking to return a list of changeLogs for a given environment, with only the environment._id to go on.

I can find the correct client document using just this _id:

db.clients.find({'products.environments._id': ObjectId("5a1bf4b410842200043d56ff")})

But this returns the entire client document. What I want is to return just the changeLogs array from the environment with _id: ObjectId("5a1bf4b410842200043d56ff")

Assuming I have the _id of the first environment of the first product, my desired output is the following:

[
  { "some": "fields21" },
  { "some": "fields22" }
]

What query would you recommend I use to achieve this?

Many thanks in advance for any help. The docs thus far have only been confusing, but I'm sure I'll get there in the end!


Solution

  • db.clients.aggregate([
      {
        $unwind: "$products"
      },
      {
        $unwind: "$products.environments" 
      },
      {
        $match: { "products.environments._id": ObjectId("5a1bf4b410842200043fffff") }
      },
      {
        $project: { _id: 0, changeLogs: "$products.environments.changeLogs" }
      }
    ]).pretty()
    

    Results in:

    {
      "changeLogs": [
        { "some": "fields21" },
        { "some": "fields22" }
      ]
    }
    

    For those finding that code confusing I found it very useful to just add one aggregate method at a time, look at the results, and then add then next method to the pipeline. Once the pipeline was complete I also experimented with removing intermediary steps to see if I could get the same results with less piping.