Search code examples
javascriptnode.jsmongodbmongooseaggregation-framework

MongoDB - How to do subqueries in aggregation


I am trying to add a field to an aggregation called friendshipStatus and it determines whether the user is a friend of the requester or not.

I have this whole query:

[
  {
    $match: (query) ? query : {}
  },
  {
    $addFields: {
      friendshipStatus: {
        $cond: {
          if: {
            $in: [vierweruserName || '', "$friends"]
          },
          then: "friend",
          else: {
            $cond: {
              if: {
                $in: [vierweruserName || '', "$friendRequests"]
              },
              then: "requested",
              else: "none"
            }
          }
        }
      }
    }
  },
  {
    $project: {
      _id: 0,
      firstName: 1,
      lastName: 1,
      userDescription: 1,
      userName: 1,
      imgURL: 1,
      coverURL: 1,
      friendshipStatus: 1 // Include the new field in the output
    }
  },
  {
    $limit: 50
  }
]

And I want to modify this addField stage to check if the user name is in the requester's friendRequests array or not (that would happen if he sent the requester a friend request)

{
  $addFields: {
    friendshipStatus: {
      $cond: {
        if: {
          $in: [vierweruserName || '', "$friends"]
        },
        then: "friend",
        else: {
          $cond: {
            if: {
              $in: [vierweruserName || '', "$friendRequests"]
            },
            then: "requested",
            else: "none"
          }
        }
      }
    }
  }
}

I am expecting the code should also add the friendshipStatus as 'requested' if the user is in the array of friendRequests in the viewerUserName document. I wonder if I can do a subquery in the addFields stage to get the viewerUserName document and then search it or if there is another way.

Sample document:

{
    "_id": "65034fed2153ebff85f30554",
    "userName": "minamelad232",
    "firstName": "mina",
    "lastName": "melad",
    "password": "$2b$10$Sr/cWOWwtAC9AQpHHLjFfuD3TKLIFvjd89QwgIJdz6cMB.rvWFeJy",
    "userDescription": "No description",
    "friends": ["minamelad123"],
    "friendRequests": ["markosami123"],
    "imgURL": "http://res.cloudinary.com/dik65evmf/image/upload/v1694726404/widgetUpl...",
    "coverURL": "http://res.cloudinary.com/dik65evmf/image/upload/v1694715952/widgetUpl...",
    "influence": 0,
    "feedOffset": 0,
    "commentsOffset": 0,
    "friendRequestsOffset": 0,
    "__v": 42    
}

Sample output:

{
    "userName": "minamelad232",
    "firstName": "mina",
    "lastName": "melad",
    "userDescription": "No description",
    "imgURL": "http://res.cloudinary.com/dik65evmf/image/upload/v1694726404/widgetUpload/qbegll5tmhumijplqkek.jpg",
    "coverURL": "http://res.cloudinary.com/dik65evmf/image/upload/v1694715952/widgetUpload/f61ztmyvln0c5p0ei1db.png",
    "friendshipStatus": "none"
}

Solution

  • Given that these are the sample (minimal) documents:

    [
      {
        "_id": ObjectId("5a934e000102030405000000"),
        "userName": "Alex",
        "friends": [],
        "friendRequests": [
          //"may"
          
        ]
      },
      {
        "_id": ObjectId("5a934e000102030405000001"),
        "userName": "May",
        "friends": [],
        "friendRequests": [
          "Alex"
        ]
      }
    ]
    

    As mentioned you need to search the viewer's friendRequests, this requires to self join the collection with userName: vierwerUsename

    1. $lookup - Search the document in the same collection with userName: vierwerUsename and return the document in the array as viewer.

    2. $addField - Would recommend using $switch operator for handling multiple conditions instead of nested $cond. For the third condition, you should match the condition with the userName in the document does existed in the friendRequest of the viewer.

    {
        $lookup: {
          from: "collection", // replace with your collection name
          pipeline: [
            {
              $match: {
                $expr: {
                  $eq: [
                    vieweruserName || '',
                    "$userName"
                  ]
                }
              }
            }
          ],
          as: "viewer"
        }
      },
      {
        $addFields: {
          friendshipStatus: {
            $switch: {
              branches: [
                {
                  case: {
                    $in: [
                      vieweruserName || '',
                      "$friends"
                    ]
                  },
                  then: "friend"
                },
                {
                  case: {
                    $in: [
                      vieweruserName || '',
                      "$friendRequests"
                    ]
                  },
                  then: "requested"
                },
                {
                  case: {
                    $in: [
                      "$userName",
                      {
                        $getField: {
                          field: "friendRequests",
                          input: {
                            $arrayElemAt: [
                              "$viewer",
                              0
                            ]
                          }
                        }
                      }
                    ]
                  },
                  then: "requested"
                }
              ],
              default: "none"
            }
          }
        }
      }
    

    Demo @ Mongo Playground