Aggregation query on MongoDB based on multiple criteria in subdocuments

Viewed 80

I have several hundred records in this format:

{
 "_id" : ObjectId( "51ac1356c59151b66c0c9b6b" ),
 "name" : "SomeString",
 "subject" : [
    "_id" : ObjectId( "51ac1356c59151b66c0c912d" ),
    "subjectName": "SomeString",
    "prerequisite": [ "subject1", "subject2"]
    ]
}

Here's what the data looks like:

[
  {
    "_id": "123456",
    "name": "StudentB",
    "subject": [
      {
        "_id": "78901",
        "subjectName": "Politics 101",
        "prerequisite": []
      },
      {
        "_id": "10987",
        "subjectName": "Geography 500",
        "prerequisite": []
      },
      {
        "_id": "78901",
        "subjectName": "Science 500",
        "prerequisite": [
          "Science 204",
          "Math 100"
        ]
      },
      {
        "_id": "54321",
        "subjectName": "Maths 600",
        "prerequisite": [
          "Science 400",
          "Math 400"
        ]
      }
    ]
  },
  {
    "_id": "654321",
    "name": "StudentA",
    "subject": [
      {
        "_id": "78901",
        "subjectName": "Music 101",
        "prerequisite": []
      },
      {
        "_id": "78901",
        "subjectName": "History 500",
        "prerequisite": [
          "History 200"
        ]
      },
      {
        "_id": "54321",
        "subjectName": "Maths 600",
        "prerequisite": [
          "Science 400",
          "Math 400"
        ]
      },
      {
        "_id": "10987",
        "subjectName": "Geography 500",
        "prerequisite": []
      }
    ]
  }
]

I am trying to construct a query to get the below result:

[
  {
    "_id": "654321",
    "name": "StudentA",
    "subject": [
      {
        "_id": "78901",
        "subjectName": "History 500",
        "prerequisite": [
          "History 200"
        ]
      },
      {
        "_id": "54321",
        "subjectName": "Maths 600",
        "prerequisite": [
          "Science 400",
          "Math 400"
        ]
      },
      {
        "_id": "10987",
        "subjectName": "Geography 500",
        "prerequisite": []
      },
      {
        "_id": "78901",
        "subjectName": "Music 101",
        "prerequisite": []
      }
    ]
  },
  {
    "_id": "123456",
    "name": "StudentB",
    "subject": [
      {
        "_id": "54321",
        "subjectName": "Maths 600",
        "prerequisite": [
          "Science 400",
          "Math 400"
        ]
      },
      {
        "_id": "78901",
        "subjectName": "Science 500",
        "prerequisite": [
          "Science 204",
          "Math 100"
        ]
      },
      {
        "_id": "10987",
        "subjectName": "Geography 500",
        "prerequisite": []
      },
      {
        "_id": "78901",
        "subjectName": "Politics 101",
        "prerequisite": []
      }
    ]
  }
]

Here's what I have tried so far:

studentDetails.aggregate([
    {
        $project: {
            '_id': 1,
            'name': 1,
            'subject.subjectName': 1,
            'subject.prerequisite': 1
        }
    },
    {
        $unwind: '$subject'
    },
    {
        $sort: {
            'subject.prerequisite': -1
        }
    },
    {
        $group: {
            _id: '$_id',
            subject: { $push: '$subject' },
            name: { $first: '$name' },
        }
    }
]);

The above aggregation returns the subdocuments sorted in reverse alphabetical order where the prerequisites are ordered from Z-A and the empty arrays are at the end of the list.

I need to sort the documents in the DB according to:

  1. The name of the students (A-Z)
  2. Name of the subject where the subjects with prerequisites are returned at the beginning of the resulting query
1 Answers

It is totally difficult to do both sorting together, because

  • when sort with subject.subjectName: 1 it will sort ascending order
  • then sort with subject.prerequisite: -1 it will sort descending order but it reset above sort

You can try this experiment,

  $unwind: ... ,  // skipped
  • sort by subjectName ascending order
  {
    $sort: {
      "subject.subjectName": 1
    }
  },
  • add new field viewOrder in subject array
  • check condition if prerequisite array size greater then 0 then set 1 otherwise 0
  {
    $addFields: {
      "subject.viewOrder": {
        $cond: {
          if: {
            $size: "$subject.prerequisite"
          },
          then: 1,
          else: 0
        }
      }
    }
  },
  • sort by viewOrder descending order
  {
    $sort: {
      "subject.viewOrder": -1
    }
  },
  • no longer needed viewOrder field
  {
    $project: {
      "subject.viewOrder": 0
    }
  },
  $group: ... ,  // skipped
  • sort by name ascending order
  {
    $sort: {
      name: 1
    }
  }

Here you have achieved your desired result.

Playground: https://mongoplayground.net/p/syg2w_d0j_b

Related