skip to Main Content

I am new to MongoDB aggregations and I am scratching my head on how to do something similar to this with Mongo:

Example SQL Query (if this was a relational DB):

select id, name, (select max(createdAt) from events where user_id=u.id) 
from users u

Or..

select users.id, users.name, max(events.created_at) 
from users inner join events on users.id=events.user_id
group by users.id, users.name

The end result is the same. I’d like to list all users, together with a max date of their latest event.

How to accomplish this on Mongo?

Assuming I have a users and events collection with same fields.

I am figuring I should start with a $lookup, which brings me the events together with the user document.

[{
    $match: {
        accountId: '629a251af534a3600aa1a150'
    }
}, {
    $lookup: {
        from: 'productevents',
        localField: 'id',
        foreignField: 'userId',
        as: 'userEvents'
    }
}, {}]

Just not sure what to do next in order to count the get max(created_at) from the remaining events collection.

Sample data:

Users:

{
  "id": "1",
  "accountId": "629a251af534a3600aa1a150",
  "name": "Some User",
  "createdAt": {
    "$date": {
      "$numberLong": "1654269244479"
    }
  },
  "properties": {
    "age": "39"
  },
  "__v": 0
}

ProductEvents:

{
  "name": "login",
  "accountId": "629a251af534a3600aa1a150",
  "userId": "1",
  "groupId": "1",
  "properties": {
    "client": "mobile"
  },
  "createdAt": {
    "$date": {
      "$numberLong": "1654269289432"
    }
  },
  "__v": 0
}

2

Answers


  1. Chosen as BEST ANSWER

    Found a way to do it (although not sure it's the optimal one).

    [
      {
        '$lookup': {
          'from': 'productevents', 
          'localField': 'id', 
          'foreignField': 'userId', 
          'as': 'events'
        }
      }, {
        '$unwind': {
          'path': '$events', 
          'preserveNullAndEmptyArrays': true
        }
      }, {
        '$group': {
          '_id': {
            'id': '$id', 
            'name': '$name', 
            '_id': '$_id', 
            'createdAt': '$createdAt', 
            'properties': '$properties'
          }, 
          'lastActivity': {
            '$max': '$events.createdAt'
          }
        }
      }, {
        '$project': {
          '_id': '$_id._id', 
          'id': '$_id.id', 
          'createdAt': '$_id.createdAt', 
          'properties': '$_id.properties', 
          'lastActivity': 1
        }
      }
    ]
    

  2. You can use the $lookup pipeline to optimize, by getting only what you need form the productevents collection:

    db.users.aggregate([
      {
        $lookup: {
          from: "productevents",
          let: {id: "$id"},
          pipeline: [
            {$match: {$expr: {$eq: ["$userId", "$$id"]}}}
            {$sort: {createdAt: -1}},
            {$limit: 1},
            {$project: {createdAt: 1, _id: 0}}
          ],
          as: "lastActivity"
        }
      },
      {
        $set: {lastActivity: {$arrayElemAt: ["$lastActivity", 0]}}
      },
      {
        $project: {
          id: 1,
          createdAt: 1,
          properties: 1,
          lastActivity: "$lastActivity.createdAt"
        }
      }
    ])
    

    Playground example

    EDIT:
    If you need to count the events per user, you can do it inside the $lookup pipeline as well:

    db.users.aggregate([
      {
        $lookup: {
          from: "productevents",
          let: {id: "$id"},
          pipeline: [
            {$match: {$expr: {$eq: ["$userId", "$$id"]}}}
            {$group: {_id: 0, count: {$sum: 1}, createdAt: {$max: "$createdAt"}}}
          ],
          as: "lastActivity"
        }
      },
      {
        $set: {lastActivity: {$arrayElemAt: ["$lastActivity", 0]}}
      },
      {
        $project: {
          id: 1,
          createdAt: 1,
          properties: 1,
          lastActivity: "$lastActivity.createdAt",
          activityCount: "$lastActivity.count"
        }
      }
    ])
    

    Playground example

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search