Search code examples
mongodbmongodb-queryaggregation-frameworkmongodb-.net-driver

MongoDB aggregate group on inner child collection and get count


I have a User collection, which further have a 'UserSubscription' collection, which further have 'Subscription > Publication'.

I have following User collection in Mongo

/* 1 */
{
    "_id" : 1,
    "UserSubscriptions" : [ 
    {
        "_id" : 1,
        "Subscription" : {
            "_id" : 1,
            "Publication" : {
                "_id" : 1,
                "Code" : "1MM",
            },
    },
    {
        "_id" : 2,
        "Subscription" : {
            "_id" : 2,
            "Publication" : {
                "_id" : 2,
                "Code" : "2MM",
            },      
    },
    {
        "_id" : 7,
        "Subscription" : {
            "_id" : 7,
            "Publication" : {
                "_id" : 1,
                "Code" : "1MM",
            },      
    }
]
}

/* 2 */
{
    "_id" : 2,
    "UserSubscriptions" : [ 
    {
        "_id" : 3,
        "Subscription" : {
            "_id" : 3,
            "Publication" : {
                "_id" : 1,
                "Code" : "1MM",
            }      
        }
    ]
}

/* 3 */
{
    "_id" : 3,
    "UserSubscriptions" : [ 
    {
        "_id" : 4,
        "Subscription" : {
            "_id" : 4,
            "Publication" : {
                "_id" : 1,
                "Code" : "1MM",
            }      
        }
    ]
}

/* 4 */
{
    "_id" : 4,
    "UserSubscriptions" : [ 
    {
        "_id" : 5,
        "Subscription" : {
            "_id" : 5,
            "Publication" : {
                "_id" : 2,
                "Code" : "2MM",
            }      
       }
   ]
}

I'm trying to get all 'Publication code' and the Count (User subscribed to Subscription count). So from above collection i want result like this

 PublicationCode      Count (Number of users)
 1MM                  3
 2MM                  2 

I have tried folling MongoDB query but not getting actual result

db.runCommand(
{ aggregate : "User", pipeline : [
    {$match: { "UserSubscriptions.0": {$exists: true}} },
    {$group: {_id:"$UserSubscriptions.Subscription.Publication.Code", count:{$sum:1}}},
    {$project: {_id:1,count:1 } }
    ]}
);

Please advice some way to use group on child collection

(Robo 3T 1.2.1) (MongoDB.Driver 2.4.4)

Corresponding C# Mongo Driver query is

var unWind = new BsonDocument
        {
            { "$unwind", "$UserSubscriptions" }
        };
var group = new BsonDocument
            {{"$group",
                new BsonDocument
                {{ "_id", new BsonDocument {{"id", 
"$UserSubscriptions.Subscription.Publication.Code"}, }},
                {"Count",  new BsonDocument {{"$sum", 1}} }}
            }};

var pipeline = new[] { unWind, group };
var ff = _readOnlyAccess.GetDatabase(this._Database).GetCollection<T> 
(this._Collection).Aggregate<T>(pipeline);

Resulted in error

FormatException: Cannot deserialize a 'Int32' from BsonType 'Document'.


Solution

  • Just use $unwind. Also, there's no need to use that $match - $unwind will skip empty arrays by default.

    db.getCollection('User').aggregate([
      {$unwind: '$UserSubscriptions'},
      {$group: {
        _id: '$UserSubscriptions.Subscription.Publication.Code',
        count: {$sum: 1}
      }}
    ])
    

    EDIT: Above will count multiple codes in customer multiple times, so here's an updated version:

    db.getCollection('User').aggregate([
      {$unwind: '$UserSubscriptions'},
      {$group: {
        _id: '$_id',
        codes: {$addToSet: '$UserSubscriptions.Subscription.Publication.Code'}
      }},
      {$unwind: '$codes'},
      {$group: {
        _id: '$codes',
        count: {$sum: 1}
      }}
    ])