简体   繁体   中英

MongoDB Aggregation for every element of array field of a document

I have 2 collections -

Student collection (sample student document)

{
'id': '123',
'name': 'john',
'age': 25,
'fav_colors': ['red', 'black'],
'marks_in_subjects': [
    {
     'marks': 90,
     'subject_id': 'abc'
    },
    {
     'marks': 92,
     'subject_id': 'def'
    }
 ]
}

Subjects collection (2 sample documents)

{
'id': 'abc',
'name': 'math'
},
{
'id': 'def',
'name': 'physics'
}

When I query for student document for id: '123', I want the resulting output as:

{
'id': '123',
'name': 'john',
'age': 25,
'fav_colors': ['red', 'black'],
'marks_in_subjects': [
    {
     'marks': 90,
     'subject_id': 'abc',
     'subject_name': 'math'
    },
    {
     'marks': 92,
     'subject_id': 'def',
     'subject_name': 'physics'
    }
 ]
}

Now, I read the MongoDB aggregation pipelines and operators document, still, I am clueless as to how to achieve this. The doubt persists because I am not even sure if this is possible with the help of mongo aggregation pipelines since JOIN happens here for every element of the array field in the student document.

It would be really helpful if anyone can help here. Thanks

Demo - https://mongoplayground.net/p/H5fHpfWz5VH

db.Students.aggregate([
  {
    $unwind: "$marks_in_subjects" //  break into individual documents
  },
  {
    "$lookup": { // get subject details
      "from": "Subjects",
      "localField": "marks_in_subjects.subject_id",
      "foreignField": "id",
      "as": "subjects"
    }
  },
  {
    $set: { // set name
      "marks_in_subjects.name": "subjects.0.name" // pick value from 0 index
    }
  },
  {
    $group: { // join document back by id
      _id: "$_id",
      marks_in_subjects: { $push: "$marks_in_subjects" }
    }
  }
])
  • $match you conditions
  • $unwind deconstruct marks_in_subjects array
  • $lookup with subjects collection
  • $addFields to get first element name from return subject
  • $group by id and reconstruct marks_in_subjects array and also add your required field of root document using $first operator
db.students.aggregate([
  { $match: { id: "123" } },
  { $unwind: "$marks_in_subjects" },
  {
    $lookup: {
      from: "subjects",
      localField: "marks_in_subjects.subject_id",
      foreignField: "id",
      as: "marks_in_subjects.subject_name"
    }
  },
  {
    $addFields: {
      "marks_in_subjects.subject_name": {
        $arrayElemAt: ["$marks_in_subjects.subject_name.name", 0]
      }
    }
  },
  {
    $group: {
      _id: "$id",
      name: { $first: "$name" },
      age: { $first: "$age" },
      fav_colors: { $first: "$fav_colors" },
      marks_in_subjects: { $push: "$marks_in_subjects" }
    }
  }
])

Playground


Second option without $unwind stage,

  • $match you conditions
  • $lookup with subjects collection
  • $addFields to get subject name from subjects
    • $map to iterate loop of marks_in_subjects array
    • $reduce to iterate loop of subjects array and check condition if subject_id match then return subject name
    • $mergeObjects to merge current object of marks_in_subjects and new field subject_name
  • $unset to remove subjects array because its not needed now
db.students.aggregate([
  { $match: { id: "123" } },
  {
    $lookup: {
      from: "subjects",
      localField: "marks_in_subjects.subject_id",
      foreignField: "id",
      as: "subjects"
    }
  },
  {
    $addFields: {
      marks_in_subjects: {
        $map: {
          input: "$marks_in_subjects",
          as: "m",
          in: {
            $mergeObjects: [
              "$$m",
              {
                subject_name: {
                  $reduce: {
                    input: "$subjects",
                    initialValue: "",
                    in: {
                      $cond: [{ $eq: ["$$this.id", "$$m.subject_id"]}, "$$this.name", "$$value"]
                    }
                  }
                }
              }
            ]
          }
        }
      }
    }
  },
  { $unset: "subjects" }
])

Playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM