2

I'm developping an api with mongoose and nodejs.

Basically there is a Course Model, Student Model and Group Model. For every course there is two fields in course document:

. Field "students": array of objectids referencing Student Model.

. Field "groups": array of subdocuments, each subdocument has a "groupId" field referencing Group Model and a "students" field: an array of objectids referencing Student Model. These are the students enrolled in the group by course.

I can get the sorted student list by course, but I can't get this list by group. I can get the list unsorted, but not sorted.

These are my main schemas:

Group Model -> groups collection:

const groupSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    unique: true
  }
})

Student Model -> students collection:

const studentSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    index:true
  },
  firstName: {
    type: String,
    required: true,
    index:true
  },
  lastName: {
    type: String,
    required: true,
    index:true
  }
})

Course Model -> courses collection:

const courseSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    index: true
  },
  students: [
    {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Student'
    }
  ],
  groups: [
    {
      groupId: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Group'
      },
      students: [
        {
          type: mongoose.Schema.Types.ObjectId,
          ref: 'Student'    
        }
      ],
    }
  ]
})

Using next query I get an alphabetically sorted student list by course, even I can filter with "where" without problems:

const query = await Course.findById(courseId)
    .select('students')
    .populate({
      path: 'students',
      select: ['_id', 'firstName', 'lastName', 'name'],
      match: where,
      options: {
        sort: {
          firstName: 1,
          lastName: 1,
          name: 1
        }
      }
    })

This query is fine,but I want another query to return the ordered student list by group and course. I'm using a similar query, but with a nested subdocuments array:

  const query = await Course.findById(courseId)
    .where('groups.groupId').equals(groupId)
    .select('groups.$.students')
    .populate({
      path: 'groups.students',
      select: ['_id', 'firstName', 'lastName', 'name'],
      match: where,
      // options: {
      //   sort: {
      //     firstName: 1,
      //     lastName: 1,
      //     name: 1
      //   }
      // }
    })

This way I get the student list, but unsorted. If I uncomment the options to sort the populated list, I get the error:

"Cannot populate with sort on path groups.students because it is a subproperty of a document array".

I'm newbie in mongodb, before I worked with sql databases, but I'm learning mongodb with mongoose. I don't know if my schema design is the best to get what I want.

As this is not working, to fix the problem I apply the "sort" method to the query to sort, like this:

  query.groups[0].students.sort((st1, st2) => {
    if (st1.firstName > st2.firstName) return 1
    if (st1.lastName > st2.lastName) return 1
    if (st1.name > st2.name) return 1
    return -1
  })

Is there a way to get this using the query and/or populate methods?

Following the expert advices about mongodb aggregation framework I find out my first approach to solve my problem. The aggregate I've used:

db.courses.aggregate([
    { $match: { _id: ObjectId("5c6d43c98068bc0836a62b65") }},
    { $project: {
        _id: 0,
        groups: '$groups'
        }
    },
    { $unwind: "$groups" },
    { $match: { 'groups.groupId': ObjectId("5c94b0d81ce16357d74549dd") }},
    { $project: {
        students: '$groups.students'
    }},
    { $unwind: '$students' },
    { $lookup: {
        from: "students",
        localField: "students",
        foreignField: "_id",
        as: "students"
    }},
    { $project: {
        id: { $arrayElemAt: [ "$students._id", 0 ] },
        nie: { $arrayElemAt: [ "$students.nie", 0 ] },
        name: { $arrayElemAt: [ "$students.name", 0 ] },
        firstName: { $arrayElemAt: [ "$students.firstName", 0 ] },
        lastName: { $arrayElemAt: [ "$students.lastName", 0 ] }
    }},
    { $sort: { firstName: 1, lastName: 1, name: 1 } }
])
  • 2
    As the error notes, `populate()` does not work on embedded documents. The only way to get MongoDB to return a "sorted array" is through an aggregation pipeline. Likewise, `$lookup` is better suited to modern deployments than `populate()`. In reality though, if you don't want to do anything else with the inner arrays than sort them, you probably should just post process the results. Ideally coming from a nested `$lookup`, which can sort it's result arrays. All of those concepts are demonstrated on the linked answers. Particular with the `$lookup` case, which also shows populate approaches – Neil Lunn Mar 25 '19 at 11:50

0 Answers0