Home > Software design >  MongoDB aggregate. Create new groups for non-existing items
MongoDB aggregate. Create new groups for non-existing items

Time:08-24

My collection of documents contains information about users, their sessions and CRUD operations they performed during these sessions:

{
    user_id: '1',
    sessions: [
        {
            actions: [
                {
                    type: 'create',
                    created_at: ISODate('2020-01-01T00:00:00'),
                },
                {
                    type: 'read',
                    created_at: ISODate('2022-01-01T00:00:00'),
                },
                {
                    type: 'read',
                    created_at: ISODate('2021-01-01T00:00:00'),
                }
            ],
        }
    ]
}

I need to get a summary for each user, which includes the amount of CRUD operations and the date of the last one:

{
    user_id: '1',
    actions: [
        {
            type: 'create',
            last: ISODate('2020-01-01T00:00:00'),
            count: 1,
        },
        {
            type: 'read',
            last: ISODate('2022-01-01T00:00:00'),
            count: 2,
        },
        // Problematic part:
        {
            type: 'update',
            last: null,
            count: 0,
        },
        {
            type: 'delete',
            last: null,
            count: 0,
        },
    ]
}

I came up with this solution:

db.users.aggregate([
    {$unwind:'$sessions'},
    {$unwind:'$sessions.actions'},
    {
        $group:{
            _id:{user_id:'$user_id', type:'$sessions.actions.type'},
            last:{$max:'$sessions.actions.created_at'},
            count:{$sum:1},
        }
    },
    {
        $group:{
            _id:{user_id:'$_id.user_id'},
            actions:{$push:{type:'$_id.type', last:'$last', count:'$count'}}
        }
    },
    {
        $project:{
            _id:0,
            user_id: '$_id.user_id',
            actions: '$actions'
        }
    }
])

The problem here is that I cannot figure out, how can I add missing actions, like in 'update' and 'delete' in the example above

CodePudding user response:

Try this,

db.collection.aggregate([
  {
    $unwind: "$sessions"
  },
  {
    $unwind: "$sessions.actions"
  },
  {
    $group: {
      _id: {
        user_id: "$user_id",
        type: "$sessions.actions.type"
      },
      last: {
        $max: "$sessions.actions.created_at"
      },
      count: {
        $sum: 1
      },
      
    }
  },
  {
    $group: {
      _id: {
        user_id: "$_id.user_id"
      },
      actions: {
        $push: {
          type: "$_id.type",
          last: "$last",
          count: "$count"
        }
      }
    }
  },
  {
    $project: {
      _id: 0,
      user_id: "$_id.user_id",
      actions: {
        "$function": {
          "body": "function(doc) { const ops = {read:0, delete:0, update: 0, create: 0}; const actions = doc.actions; actions.forEach(action => { ops[action.type] = 1 }); Object.keys(ops).filter(key => ops[key] === 0).forEach(key => actions.push({count: 0, last: null, type: key})); return actions }",
          "args": [
            "$$ROOT"
          ],
          "lang": "js"
        }
      },
    }
  },
])

Here, we use $function and provide a small JS function to populate the missing entries.

Playground link.

  • Related