Search code examples
mongodbmongodb-queryaggregation-frameworkmongodb-aggregation

Mongodb use multiple group operator in single aggregation


I am using mongodb aggregation for getting counts of different fields. Here are some documents from the mobile collection:-

{
  "title": "Moto G",
  "manufacturer": "Motorola",
  "releasing": ISODate("2011-03-00T10:26:48.424Z"),
  "rating": "high"
}
{
  "title": "Asus Zenfone 2",
  "manufacturer": "Asus",
  "releasing": ISODate("2014-10-00T10:26:48.424Z"),
  "rating": "high"
}
{
  "title": "Moto Z",
  "manufacturer": "Motorola",
  "releasing": ISODate("2016-10-12T10:26:48.424Z"),
  "rating": "none"
}
{
  "title": "Asus Zenfone 3",
  "manufacturer": "Asus",
  "releasing": ISODate("2016-08-00T10:26:48.424Z"),
  "rating": "medium"
}

I can find manufacturer and rating counts but this fails:

db.mobile.aggregate([
    {
        $group: { _id: "$manufacturer", count: { $sum: 1 } }
    }, {
        $group: { _id: "$rating", count: { $sum: 1 } }
    }
])

Output:-

{
    "_id" : null,
    "count" : 2.0
}

Expected Output something like:-

  {
      "_id":"Motorola",
      "count" : 2.0
  }
  {
      "_id":"Asus",
      "count" : 2.0
  } 
  {
      "_id":"high",
      "count" : 2.0
  }
  {
      "_id":"none",
      "count" : 1.0
  }
  {
      "_id":"medium",
      "count" : 1.0
  }

Solution

  • I believe you are after an aggregation operation that groups the documents by the manufacturer and rating keys, then do a further group on the manufacturer while aggregating the ratings per manufacturer, something like the following pipeline:

    db.mobile.aggregate([
        {
            "$group": {
                "_id": { 
                    "manufacturer": "$manufacturer",
                    "rating": "$rating"
                },
                "count": { "$sum": 1 }
            }
        },
        { 
            "$group": {
                "_id": "$_id.manufacturer",
                "total": { "$sum": 1 },
                "counts": {
                    "$push": {
                        "rating": "$_id.rating",
                        "count": "$count"
                    }
                }
            }
        }
    ])
    

    Sample Output

    /* 1 */
    {
        "_id" : "Motorola",
        "total" : 2,
        "counts" : [ 
            {
                "rating" : "high",
                "count" : 1
            }, 
            {
                "rating" : "none",
                "count" : 1
            }
        ]
    }
    
    /* 2 */
    {
        "_id" : "Asus",
        "total" : 2,
        "counts" : [ 
            {
                "rating" : "high",
                "count" : 1
            }, 
            {
                "rating" : "medium",
                "count" : 1
            }
        ]
    }
    

    or if you are after a more "flat" or "denormalised" result, run this aggregate operation:

    db.mobile.aggregate([
        { 
            "$group": { 
                "_id": "$manufacturer",  
                "total": { "$sum": 1 },           
                "high_ratings": {
                    "$sum": {
                        "$cond": [ { "$eq": [ "$rating", "high" ] }, 1, 0 ]
                    }
                },
                "medium_ratings": {
                    "$sum": {
                        "$cond": [ { "$eq": [ "$rating", "medium" ] }, 1, 0 ]
                    }
                },
                "low_ratings": {
                    "$sum": {
                        "$cond": [ { "$eq": [ "$rating", "low" ] }, 1, 0 ]
                    }
                },            
                "none_ratings": {
                    "$sum": {
                        "$cond": [ { "$eq": [ "$rating", "none" ] }, 1, 0 ]
                    }
                }           
            }  
        }
    ])
    

    Sample Output

    /* 1 */
    {
        "_id" : "Motorola",
        "total" : 2,
        "high_ratings" : 1,
        "medium_ratings" : 0,
        "low_ratings" : 0,
        "none_ratings" : 1
    }
    
    /* 2 */
    {
        "_id" : "Asus",
        "total" : 2,
        "high_ratings" : 1,
        "medium_ratings" : 1,
        "low_ratings" : 0,
        "none_ratings" : 0
    }