Home > OS >  extract subdocument in mongoDB
extract subdocument in mongoDB

Time:04-14

I am new to MongoDB and I'm struggling in extract a subdocument from a document. In my case, I have the following schema:

    [
      {
        user_id: "u1",
        subscriptions: [
          {
            "tier": "Basic",
            "requests": [
              {
                "name": "aaaa"
              },
              {
                "name": "bbbb"
              }
            ]
          },
          {
            "tier": "Premium",
            "requests": [
              {
                "name": "cccc"
              },
              {
                "name": "dddd"
              }
            ]
          }
        ]
      },
      {
        user_id: "u2",
        subscriptions: [
          {
            "tier": "Premium",
            "requests": [
              {
                "name": "eeee"
              },
              {
                "name": "ffff"
              }
            ]
          }
        ]
      }
    ]

In my case I would like to get ONLY the list of requests for user 1. This is what I would like to get :

[
  { "name": "aaaa" },
  { "name": "bbbb" },
  { "name": "cccc" },
  { "name": "dddd" }
]

How can I fix it ?

CodePudding user response:

Use $unwind and then $project

db.collection.aggregate([
  {
    $match: {
      user_id: "u1"
    }
  },
  {
    $unwind: "$subscriptions"
  },
  {
    $unwind: "$subscriptions.requests"
  },
  {
    $project: {
      name: "$subscriptions.requests.name"
    }
  },
  {
    $unset: "_id"
  }
])

mongoplayground

CodePudding user response:

A simple way will be:

db.collection.aggregate([
  {
    $match: {
      user_id: "u1"
    }
  },
  {
    $unwind: "$subscriptions"
  },
  {
    $unwind: "$subscriptions.requests"
  },
  {
    $project: {
      "name": "$subscriptions.requests.name",
      _id: 0
    }
  }
])

As you can see on the playground

The $unwind separates the array elements into single documents. The $project allows to format the document

CodePudding user response:

With the aggregation framework, you can create a pipeline that first filters out documents in the collection with the given condition using the following a $match pipeline

{ $match: { user_id: 'u1' } }

The next pipeline will need to display the desired output using $project pipeline stage and the expression to achieve this will use a $reduce array operator.

This operator enables you to flatten the 2d array expression '$subscriptions.requests' which mongo resolves from this

[
  [
     { "name": "aaaa" },
     { "name": "bbbb" }
  ],
  [
     { "name": "cccc" },
     { "name": "dddd" }
  ]
]

by concatenating the inner arrays with [$concatArrays] operator 3 to just

[
  { "name": "aaaa" },
  { "name": "bbbb" },
  { "name": "cccc" },
  { "name": "dddd" }
]

without the need to introduce a couple of $unwind steps which can potentially be a huge performance cost when you have large arrays because it produces a copy of each document per array entry, which uses more memory (possible memory cap on aggregation pipelines of 10% total memory) and thus also takes time to produce/process and that's not really necessary.

The $reduce expression in this case becomes

{
  $reduce: {
     input: '$subscriptions.requests',
     initialValue: [],
     in: { $concatArrays: ['$$value', '$$this'] }
  }
} 

Thus your overall aggregation operation follows:

db.collection.aggregate([
  { $match: { user_id: 'u1' } },
  { $project: {
     _id: 0,
     requests: {
        $reduce: {
           input: '$subscriptions.requests',
           initialValue: [],
           in: {$concatArrays: ['$$value', '$$this']}
        }
     } 
  } }
])

Mongo Playground

CodePudding user response:

db.collection.aggregate([
{
    $unwind: {path:"$subscriptions"}
},
{
    $addFields :
    {
        "request": "$subscriptions.requests"
    }
},
{
    $unwind : "$request"
},
{
    $addFields : 
    {
        "name": "$request.name"
    }
},
{
    $project :
    {
        _id : 0,
        name: 1
    }
}
])
.toArray((err,doc)=>
{
    if(!err)
    {
      console.log(doc)
    }
})

Result will be

[
    {
        "name" : "aaaa"
    },  
    {
        "name" : "bbbb"
    },
    {
        "name" : "ddd"
    },
    {
        "name" : "eee"
    },
    {
        "name" : "fff"
    },
    {
        "name" : "ggg"
    }
]
  • Related