Search code examples
mongodbmongooseaggregate

Mongoose aggregation and multi level group


I have MongoDB database with lot of different coins and need to aggregate them in multilevel output but not sure how. I am using mongoose in backend and need to create route with express that will get coins and sort them in the way that I need in order to be easier for me on frontend side inside app. I found lot of examples of multilevel group here but non of them did not match my situation and I can get it work.

INPUT:

[
  {
    "_id": "643d5bf4637daa055466d8f0",
    "country": "AND",
    "denomination": 0.01,
    "series": 1
  },
  {
    "_id": "643d5bf4637daa055466d8f0",
    "country": "AND",
    "denomination": 0.01,
    "series": 1
  },
  {
    "_id": "643d5bf4637daa055466d8f0",
    "country": "BEL",
    "denomination": 0.01,
    "series": 1
  },
  {
    "_id": "643d5bf4637daa055466d8f0",
    "country": "BEL",
    "denomination": 0.02,
    "series": 1
  },
  {
    "_id": "643d5bf4637daa055466d8f0",
    "country": "BEL",
    "denomination": 0.5,
    "series": 2
  },
  {
    "_id": "643d5bf4637daa055466d8f0",
    "country": "ESP",
    "denomination": 0.2,
    "series": 1
  },
  {
    "_id": "643d5bf4637daa055466d8f0",
    "country": "ESP",
    "denomination": 0.05,
    "series": 3
  }
]

and I need output like this

OUTPUT:

[
  {
    "country": "AND",
    "series": [
      {
        "series": 1,
        "coins": [
          {
            "_id": "643d5bf4637daa055466d8f0",
            "country": "AND",
            "denomination": 0.01,
            "series": 1
          },
          {
            "_id": "643d5bf4637daa055466d8f0",
            "country": "AND",
            "denomination": 0.01,
            "series": 1
          }
        ]
      }
    ]
  },
  {
    "country": "BEL",
    "series": [
      {
        "series": 1,
        "coins": [
          {
            "_id": "643d5bf4637daa055466d8f0",
            "country": "BEL",
            "denomination": 0.01,
            "series": 1
          },
          {
            "_id": "643d5bf4637daa055466d8f0",
            "country": "BEL",
            "denomination": 0.02,
            "series": 1
          }
        ]
      },
      {
        "series": 2,
        "coins": [
          {
            "_id": "643d5bf4637daa055466d8f0",
            "country": "BEL",
            "denomination": 0.5,
            "series": 2
          }
        ]
      }
    ]
  },
  {
    "country": "ESP",
    "series": [
      {
        "series": 1,
        "coins": [
          {
            "_id": "643d5bf4637daa055466d8f0",
            "country": "ESP",
            "denomination": 0.2,
            "series": 1
          }
        ]
      },
      {
        "series": 3,
        "coins": [
          {
            "_id": "643d5bf4637daa055466d8f0",
            "country": "ESP",
            "denomination": 0.05,
            "series": 2
          }
        ]
      }
    ]
  }
]

so far I got this done but don't know how to push coins in right series from this point.

const coins = await Coin.aggregate([
    {
      $group: {
        _id: '$country',
        series: { $addToSet: { series: '$series', coins: [] } },
      },
    },
    {
      $addFields: { country: '$_id' },
    },
    {
      $project: {
        _id: 0,
      },
    },
    {
      $sort: { country: 1 },
    },
 ]);

Solution

  • a possible approach is

    1. group by country and series
    2. regroup by country only and push the grouped series and coins to a series array
    3. project the fields you want and sort

    you can see the intermediate stages on the Stage dropdown in the playground

    db.collection.aggregate([
      { $group: { _id: { country: "$country", series: "$series" }, coins: { $push: "$$ROOT" } } },
      { $group: { _id: "$_id.country", series: { $push: { series: "$_id.series", coins: "$coins" } } } },
      { $addFields: { country: "$_id" } },
      { $project: { _id: 0 } },
      { $sort: { country: 1 } }
    ])
    

    or you can $project directly without $addFields

    { $project: { _id: 0, country: "$_id", series: 1 } }