$Push if Object doesn't already exist

Viewed 664

I have an array of students in my Documents VirtualClass and my goal is to create a roster with no duplication of students.

I want to match the VirtualClass with the teacher and then $push the student into an array.

Virtual Class Schema

const VirtualClassSchema = new Schema({
  name: { type: String, required: true },
  descriptionHeading: { type: String, required: true },
  room: { type: String },
  teacher: {
    userId: { type: Schema.Types.ObjectId, ref: "User" },
    name: { type: String },
    profile_picture: { type: String }
  },
  students: [
    {
      userId: { type: Schema.Types.ObjectId, ref: "User" },
      name: { type: String },
      profile_picture: { type: String }
    }
  ],
...

My current query is as follows

      VirtualClass.aggregate([
        { $match: { "teacher.userId": user._id } },
        {
          $group: {
            _id: null,
            students: { $addToSet: "$students" }
          }
        }
      ])

Which returns:

[
    {
        "_id": null,
        "students": [
            [
                {
                    "_id": "5e84d1a1ab3ebf54283b8cb2",
                    "userId": "5dd27452592f600900235945",
                    "name": "student  zero",
                    "profile_picture": "https://productionstemulistorage.blob.core.windows.net/stemuli/profile-picture-6e609f3b-44cb-44c0-888a-1b6767e3072d"
                }
            ],
            [
                {
                    "_id": "5e84d1a1ab3ebf54283b8cb4",
                    "userId": "5dd27452592f600900235945",
                    "name": "student  zero",
                    "profile_picture": "https://productionstemulistorage.blob.core.windows.net/stemuli/profile-picture-6e609f3b-44cb-44c0-888a-1b6767e3072d"
                }
            ]
        ]
    }
]

Expected results:

_id: null,
"students":
[
     {
       "_id": "5e84d1a1ab3ebf54283b8cb4",
        "userId": "5dd27452592f600900235945",
        "name": "student  zero",
        "profile_picture": "https://productionstemulistorage.blob.core.windows.net/stemuli/profile-picture-6e609f3b-44cb-44c0-888a-1b6767e3072d"
    }
]

Thank you!

2 Answers

You could use exclusion to remove the id from the document before sending it to $addToSet. Also your student is array so you would need $unwind.

Something like

VirtualClass.aggregate([
  {$match:{"teacher.userId": user._id}},
  {$project:{"students._id": 0}},
  {$unwind: "$students"},
  {$group:{
      _id: null,
      students: {$addToSet:"$students"}
  }}
])

example here - https://mongoplayground.net/p/zULrmihM03z

Output

[
  {
    "_id": null,
    "students": [
      {
        "name": "student  zero",
        "profile_picture": "https://productionstemulistorage.blob.core.windows.net/stemuli/profile-picture-6e609f3b-44cb-44c0-888a-1b6767e3072d",
        "userId": "5dd27452592f600900235945"
      }
    ]
  }
]

Another alternative would be to set _id to false so mongoose will not generate any id for student.

Something like

 students: [
    {
      userId: { type: Schema.Types.ObjectId, ref: "User" },
      name: { type: String },
      profile_picture: { type: String },
      _id:false
    }
  ],

You can then use aggregation query without the exclusion.

VirtualClass.aggregate([
  {$match:{"teacher.userId": user._id}},
  {$unwind: "$students"},
  {$group:{
      _id: null,
      students: {$addToSet:"$students"}
  }}
])

You need to $unwind and then $project to remove _id before $group in your aggregate function.

Code:

[
  {
    "$match": {
      "teacher.userId": 1
    }
  },
    {
    "$unwind": "$students"
  },
    {
    "$project": {
      "students._id": 0
    }
  },
  {
    "$group": {
      "_id": null,
      "students": {
        "$addToSet": "$students"
      }
    }
  }

]

Example code and Pipeline Explanation

Related