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!

FelixFortis
  • 684
  • 6
  • 16

3 Answers3

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).

ambianBeing
  • 3,449
  • 2
  • 14
  • 25
  • 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 Nov 19 '19 at 09:43
  • @FelixFortis Not sure why you are ending up with no results. Do check the [plyaground link](https://mongoplayground.net/p/WO6aorRxa5b) where the query does indeed get required result from the sample set in the question. – ambianBeing Nov 19 '19 at 10:05
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" } ] }
prasad_
  • 12,755
  • 2
  • 24
  • 36
  • Your code looks a lot like the one I finally came up with, but is giving me syntax errors. I've upvoted, thanks!. – FelixFortis Nov 19 '19 at 09:44
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.

FelixFortis
  • 684
  • 6
  • 16