'MongoDB: nested $group and $push but instead of get an array get a document

I have a couple million of bulky documents with a structure similar to this one (but with way more fields):

{ "class": 3, "type": "A", "color": "r", "filed1": "something", ... }
{ "class": 3, "type": "A", "color": "r", "filed1": "_", ... }
{ "class": 3, "type": "A", "color": "r", "filed1": "45", ... }
{ "class": 3, "type": "A", "color": "g", "filed1": "yt", ... }
{ "class": 3, "type": "B", "color": "b", "filed1": "xx", ... }
{ "class": 1, "type": "A", "color": "r", "filed1": "ds", ... }
{ "class": 1, "type": "A", "color": "r", "filed1": "bb", ... }
{ "class": 1, "type": "A", "color": "r", "filed1": "go", ... }
{ "class": 1, "type": "B", "color": "b", "filed1": "aa", ... }
{ "class": 1, "type": "B", "color": "g", "filed1": "ññ", ... }

And I want to group them by class, then by type and then by color so it looks like this.

{
   _id: 3,
   A: { 
      r: [ { field1: "something", ... }, { field1: "_", ... }, { field1: "45", ... } ],
      g: [ { field1: "yt", ... } ],
   }
   B: {
      b: [ { field1: "xx", ... } ],
   }
}
{
   _id: 1,
   A: { 
      r: [ { field1: "ds", ... }, { field1: "bb", ... }, { field1: "go", ... } ]
   }
   B: {
      b: [ { field1: "aa", ... } ],
      g: [ { field1: "ññ", ... } ],
   }
}

So far I have tried to $group the first level and then "unpack" the array using $arrayToObject.

{ 
    $group: {
        "_id": "$class",
        data: { $push: { k: "$type",  v: { color: "$color", field1: "$field1", ... } } }
    }
},
{
    $project: {
        _id: 1,
        data: { $arrayToObject: "$data" }
    }
}

But this results in both the undesired field "data" and truncation of the documents.

{
   _id: 3,
   data: {
       A: { "color": "g", "filed1": "yt", ... },
       B: { "color": "b", "filed1": "xx", ... }
   }
}
{
   _id: 1,
   data: {
       A: { "color": "r", "filed1": "go", ... },
       B: { "color": "g", "filed1": "ññ", ... }
   }
}

I am trying to avoid using the javascript function forEach because of the size of the collection (about 2 GB) and the low specs of the server, but if you think there is no other way feel free to tell me.



Solution 1:[1]

db.collection.aggregate([
  {
    $group: {
      _id: {
        class: "$class",
        type: "$type",
        color: "$color"
      },
      v: { $push: { filed1: "$filed1", field2: "$field2" } }
    }
  },
  {
    $group: {
      _id: {
        class: "$_id.class",
        type: "$_id.type"
      },
      docs2: { $push: { k: "$_id.color", v: "$v" } }
    }
  },
  {
    $group: {
      _id: "$_id.class",
      docs3: { $push: { k: "$_id.type", v: { $arrayToObject: "$docs2" } } }
    }
  },
  {
    $replaceWith: { $mergeObjects: [ { _id: "$_id" }, { $arrayToObject: "$docs3" } ] }
  }
])

mongoplayground

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 YuTing