0

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!

3 Answers 3

1

The idea here is to $unwind the products array so that its environments can be fed as input to $filter after a $match on the _id.

(lets assume the enviroment _id is 1)

db.collection.aggregate([
  {
    $unwind: "$products"
  },
  {
    $match: {
      "products.environments._id": 1
    }
  },
  {
    $project: {
      "logsArray": {
        $filter: {
          input: "$products.environments",
          as: "env",
          cond: {
            $eq: [
              "$$env._id",
              1
            ]
          }
        }
      }
    }
  },
  {
    $unwind: "$logsArray"
  }
])

O/P Should be like:

[
  {
    "_id": ObjectId("5a934e000102030405000000"),
    "logsArray": {
      "changeLogs": [
        {
          "some": "fields21"
        },
        {
          "some": "fields22"
        }
      ],
      "id": 1,
      "name": "myEnvironment1"
    }
  }
]

Note: notice the last stage $unwind of logsArray which I think is just pretty-fying the ouput. Otherwise without it also the resultant is acceptable (if you agree, can remove that).

Sign up to request clarification or add additional context in comments.

2 Comments

I've upvoted because your explanation helped me to understand how to come to the right answer, thanks! However your $project and following $unwind seem to leave me with no results.
@FelixFortis Not sure why you are ending up with no results. Do check the plyaground link where the query does indeed get required result from the sample set in the question.
1

This is just another way of doing the aggregation query. This gets the desired result.

Note I am using the "name" field of the "environments" from the sample document you had provided. The "name" can be substituted with "id" as needed.

var ENV = "myEnvironment1";

db.env.aggregate( [
  { $match: { 
  { $unwind: "$products" },
  { $unwind: "$products.environments" },
  { $match: { "products.environments.name": ENV} },
  { $project: { _id: 0, changeLogs: "$products.environments.changeLogs" } },
] )

The result:

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

If the variable ENV's value is changed, then the result will be accordingly; e.g.,: ENV = "myEnv1";

{ "changeLogs" : [ { "some" : "fields1" }, { "some" : "fields2" } ] }
{ "changeLogs" : [ { "some" : "fields3" }, { "some" : "fields4" } ] }

1 Comment

Your code looks a lot like the one I finally came up with, but is giving me syntax errors. I've upvoted, thanks!.
0
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.