Smith Dwayne
Smith Dwayne

Reputation: 2807

Mongodb Aggregation Lookup on Nested Array Fields overwriting other fields

Here are the details of my collections, I have 4 collections.

Chat,
ChatUser,
Messages,
User

Fields of these collections are,

Chat:
    _id,
    type ('dual', 'group')

ChatUser:
    _id,
    userId (Ref: Users)
    chatId (Ref: Chat)

Messages:
    _id,
    chatId (Ref: Chat)
    fromUserId (Ref: Chat)
    type: ('text', 'media')
    message

Users:
    _id,
    name,
    avatar

I wrote an aggregate function to collect all my chats below.

let chats = await Chat.aggregate([
    {$match: {_id: {$in: chatsOfUser}}},
    {
        $lookup: {
            from: "chatusers",
            let: {cId: "$_id"},
            pipeline: [
                // <-- Added Pipeline
                {
                    $match: {
                        $expr: {
                            $and: [
                                {$eq: ["$chatId", "$$cId"]},
                                {$ne: ["$userId", mongoose.Types.ObjectId(req.user._id)]},
                            ],
                        },
                    },
                },
            ],
            as: "ChatUser",
        },
    },
    {$unwind: "$ChatUser"},
    {$unwind: "$ChatUser.userId"},
    {
        $lookup: {
            from: "users",
            localField: "ChatUser.userId",
            foreignField: "_id",
            as: "Users",
        },
    },
    {$unwind: "$Users"},
    {$project: {"Users.password": 0}},
    {
        $lookup: {
            from: "messages",
            localField: "_id",
            foreignField: "chatId",
            as: "Messages",
        },
    },
    {$sort: {"Messages.createdAt": -1}},
]);

My actual result is,

{
    "chats": [
        {
            "_id": "60db388deb35c276cd023f32",
            "type": "dual",
            "ChatUser": {
                "_id": "60db388deb35c276cd023f36",
                "chatId": "60db388deb35c276cd023f32",
                "userId": "60db387feb35c276cd023f1f",
            },
            "Users": {
                "_id": "60db387feb35c276cd023f1f",
                "firstName": "Frodo",
                "lastName": "Baggins",
                "email": "[email protected]",
                "gender": "male",
            },
            "Messages": [
                {
                    "_id": "60db38f729d01c669696cb9e",
                    "message": "Hello friend",
                    "type": "text",
                    "chatId": "60db388deb35c276cd023f32",
                    "fromUserId": "60daeb5617b93e6cb968582e",// Here I want to populate the User name and avatar into a User Field
                },
                {
                    "_id": "60db38f729d01c669696cb9f",
                    "message": "Hi buddy",
                    "type": "text",
                    "chatId": "60db388deb35c276cd023f32",
                    "fromUserId": "60db387feb35c276cd023f1f", // Here I want to populate the User name and avatar into a User Field
                }
            ]
        },
    ]
}

Here I want to populate user details of 'fromUserId' in "chats.Messages.User". But If I use the following lookup, It replaces all the fields of Messages with a new User Object. Used it before sort pipeline.

{
      $lookup: {
          from: "users",
          let: { user_id: "$Messages.fromUserId" },    
          pipeline : [
              { $match: { $expr: { $eq: [ "$_id", "$$user_id" ] } }, },
              { $project : { _id:1, firstName:1 } }
          ],
          as: "Messages.User"
      }
  },

Used unwind pipeline before the above lookup,

{ "$unwind": "$Messages" },
{ "$unwind": "$Messages.fromUserId" },

But It unwinding the total chat array by the Messages. I really want to extract the fromUserId only within 'chats.Messages.User'. Is there any solution to resolve this?

Upvotes: 2

Views: 377

Answers (1)

Tom Slabbaert
Tom Slabbaert

Reputation: 22296

There are multiple ways of solving this issue, I choose what I think is the simplest approach without changing the existing pipeline too much.

I would change the second $lookup from the message to include a nested $lookup within it to fetch the user. This way we don't need to unwind the entire document and restructure the data after that.

It would look like this:

 {
    $lookup: {
      from: "messages",
      let: {
        id: "$_id"
      },
      pipeline: [
        {
          $match: {
            $expr: {
              $eq: [
                "$$id",
                "$chatId"
              ]
            }
          }
        },
        {
          $lookup: {
            from: "users",
            localField: "fromUserId",
            foreignField: "_id",
            as: "User"
          }
        },
        {
          $unwind: "$User"
        },
        {
          $project: {
            "User.password": 0
          }
        },
        
      ],
      as: "Messages",
      
    },
    
  },

Full example at:

Mongo playground

Upvotes: 1

Related Questions