Jugnu
Jugnu

Reputation: 83

How to filter sub array and return document in mongo

I have two collections Employee, Client

Employee schema has following record

{
    "_id": ObjectId("5a852dcd0290f7eca89e9a79"),
    "FirstName": "Nirav",
    "LastName": "Modi",
    "Gender": true,
    "Forms": [{
            "ClientId": ObjectId("5a8528ed0290f7eca89e9a5f"),
            "ProjectId": ObjectId("5a856fde0290f7eca89e9a88"),
            "FormId": ObjectId("5a62e561f6647f17f85e54c5")
        }]
}

Client schema has following record

{
    "_id" : ObjectId("5a8528ed0290f7eca89e9a5f"),
    "CompanyName" : "PNB",
    "Projects" : [{
            "_id" : ObjectId("5a856ca70290f7eca89e9a7f"),
            "Name" : "House Loan",
            "Description" : "Get house load",
            "Forms" : []
        }, {
            "_id" : ObjectId("5a856fde0290f7eca89e9a88"),
            "Name" : "Car Loan",
            "Description" : "get car loan",
            "Forms" : [
                ObjectId("5a62e82299d9fe0a14a1ead5"),
                ObjectId("5a6eec263bf43426d4d31780"),
                ObjectId("5a62e561f6647f17f85e54c5")
            ]
        }
    ]
}

In my system Employees are assigned forms to work on. as you can see employee schema contains Forms field which contains ClientId, ProjectId, FormId.

Form can be in multiple client for multiple project.

When I fetch data I want output be like as below

{
    "_id": ObjectId("5a852dcd0290f7eca89e9a79"),
    "FirstName": "Aartik",
    "LastName": "Ladumor",
    "Gender": true,
    "Clients": [{
            "_id": ObjectId("5a8528ed0290f7eca89e9a5f"),
            "CompanyName": "PNB",
            "Projects": [{
                    "_id": ObjectId("5a856fde0290f7eca89e9a88"),
                    "Name": "Car Loan",
                    "Description": "get car loan",
                    "Forms": [
                        ObjectId("5a62e82299d9fe0a14a1ead5"),
                        ObjectId("5a6eec263bf43426d4d31780"),
                        ObjectId("5a62e561f6647f17f85e54c5")
                    ]
                }
            ]
        }
    ]
}

Only get clients in which project contains forms that are assigned to employee.

For that Im doing aggregation as below

db.Employees.aggregate([{
            $lookup: {
                from: "Clients",
                localField: "Forms.ClientId",
                foreignField: "_id",
                as: "Clients"
            }
        }, {
              filter projects array in matched client contains only
              projects that match Employee.Forms each elements 
              ProjectsId -> Client.Projects._id and
              FormId -> in Client.Projects.Forms array containing forms ObjectId 
        }
    ]).pretty() 

Upvotes: 1

Views: 1238

Answers (2)

Saravana
Saravana

Reputation: 12817

try this aggregation, we $lookup by client id from employee collection, then $filter by project and form id from the matched clients

db.Employee.aggregate(
    [
        {$lookup : {
            from : "Client",
            localField : "Forms.ClientId",
            foreignField : "_id",
            as : "Clients"
        }},
        {$addFields : {
            "Clients.Projects" : {
                $filter : {
                input : {$arrayElemAt : ["$Clients.Projects", 0]},
                as : "project", 
                cond : {$and : [
                    {$eq : [{$arrayElemAt : ["$Forms.ProjectId", 0]}, "$$project._id"]},
                    {$in : [{$arrayElemAt : ["$Forms.FormId", 0]}, "$$project.Forms"]}
                ]}}
            }
        }}
    ]
).pretty()

Upvotes: 1

mickl
mickl

Reputation: 49985

You can try below aggregation

db.employees.aggregate([
  { $unwind: "$Forms" },
  {
    $lookup: {
        from: "clients",
        localField: "Forms.ClientId",
        foreignField: "_id",
        as: "Clients"
    }
  },
  { $unwind: "$Clients" },
  { $unwind: "$Clients.Projects" },
  {
    $redact: {
      $cond: {
        if: { $eq: [ "$Forms.ProjectId", "$Clients.Projects._id" ] },
        then: "$$KEEP",
        else: "$$PRUNE"
      }
    } 
  },
  {
    $group: {
      _id: {
        _id: "$_id",
        ClientId: "$Clients._id"
      },
      FirstName: { $first: "$FirstName" },
      LastName: { $first: "$LastName" },
      Gender: { $first: "$Gender" },
      Client: { $first: "$Clients" },
      Projects: { $push: "$Clients.Projects" }
    }
  },
  {
    $group: {
      _id: "$_id._id",
      FirstName: { $first: "$FirstName" },
      LastName: { $first: "$LastName" },
      Gender: { $first: "$Gender" },
      Clients: { $push: {
        _id: "$Client._id",
        CompanyName: "$Client.CompanyName",
        Projects: "$Projects"
      } }
    }
  }
])

Basically you have to use $unwind multiple times because you need to compare values not arrays. So $lookup merges two collections. Then you need to compare project ids so you have to $unwind again. To filter out projects that don't belong to an employee you can use $redact to compare two fields. Then to end up with two levels of nested arrays you should use $group.

Upvotes: 1

Related Questions