Search code examples
mongodbmongodb-queryaggregation-frameworkmongoose-schemamongoose-populate

Join two collections with id stored in array of objects in mongodb


I have two collections with name School & Students

School Collection

   {
      _id: ObjectId("60008e81d186a82fdc4ff2b7"),
      name: String,
      branch: String,  
      class: [{
             "active" : true,
             "_id" : ObjectId("6001e6871d985e477b61b43f"),
             "name" : "I",
             "order" : 1
        }, 
        {
             "active" : true,
             "_id" : ObjectId("6001e68f1d985e477b61b444"),
             "name" : "II",
             "order" : 2
        }]
   }

Student Collection

  {
      _id: ObjectId("6002def815eccd53a596f830"),
      schoolId: ObjectId("60008e81d186a82fdc4ff2b7"),
      sessionId: ObjectId("60008e81d186a82fdc4ff2b9"),
      class: ObjectId("6001e6871d985e477b61b43f"),
   }

I want to get the data of Student Collection in single query.

I have class id stored in Student Collection and data against that id is stored in School Collection under class key, which is array of objects.

Can you please help me in getting the class object in student collection with this id?

Output i want:

data: {
  _id: ObjectId("6002def815eccd53a596f830"),
  schoolId: ObjectId("60008e81d186a82fdc4ff2b7"),
  sessionId: ObjectId("60008e81d186a82fdc4ff2b9"),
  class: ObjectId("6001e6871d985e477b61b43f"),
  classData: [{
        "active" : true,
        "_id" : ObjectId("6001e6871d985e477b61b43f"),
        "name" : "I",
        "order" : 1
    }]

}

So I tried this but it didn't work:

const students = await this.studentModel.aggregate([
{
  $lookup: {
    from: 'School',
    let: { classId: '$class' },
    pipeline: [
      {
        $match: {
          $expr: { $eq: ['$$classId', '$class._id'] },
        },
      },
    ],
    as: 'classData',
  },
},
]);

Solution

    • $lookup, pass schoolId and class in let,
    • $match school id condition
    • $filter to iterate loop of class and filter specific class object
    • $arrayElemAt will get object from returned result from $filter
    • $replaceRoot to replace object to root
    const students = await this.studentModel.aggregate([
      {
        $lookup: {
          from: "School",
          let: {
            schoolId: "$schoolId",
            classId: "$class"
          },
          pipeline: [
            { $match: { $expr: { $eq: ["$$schoolId", "$_id"] } } },
            {
              $replaceRoot: {
                newRoot: {
                  $arrayElemAt: [
                    {
                      $filter: {
                        input: "$class",
                        cond: { $eq: ["$$this._id", "$$classId"] }
                      }
                    },
                    0
                  ]
                }
              }
            }
          ],
          as: "classData"
        }
      }
    ])
    

    Playground