Как отфильтровать подмассив и вернуть документ в монго

У меня две коллекции Employee, Client

Employee схема имеет следующую запись

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

Client схема имеет следующую запись

{
    "_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")
            ]
        }
    ]
}

В моей системе сотрудникам назначены формы для работы. как вы видите, схема сотрудника содержит Forms поле, которое содержит ClientId, ProjectId, FormId,

Форма может быть в нескольких клиентах для нескольких проектов.

Когда я получаю данные, я хочу, чтобы вывод был таким, как показано ниже

{
    "_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")
                    ]
                }
            ]
        }
    ]
}

Получать только тех клиентов, в которых проект содержит формы, назначенные сотруднику.

Для этого я делаю агрегацию, как показано ниже

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() 

2 ответа

Решение

Вы можете попробовать ниже агрегации

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"
      } }
    }
  }
])

Обычно вам нужно использовать $ unwind несколько раз, потому что вам нужно сравнивать значения, а не массивы. Таким образом, $lookup объединяет две коллекции. Затем вам нужно сравнить идентификаторы проекта, чтобы вам снова пришлось $ unwind. Чтобы отфильтровать проекты, которые не принадлежат сотруднику, вы можете использовать $ redact для сравнения двух полей. Затем, чтобы получить два уровня вложенных массивов, вы должны использовать $ group.

Попробуйте эту агрегацию, мы $lookup по идентификатору клиента из коллекции сотрудников, затем $filter по проекту и идентификатору формы из соответствующих клиентов

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()
Другие вопросы по тегам