Sort Sub Documents in MongoDB

Viewed 10315

Is there a way to sort sub documents in a mongo query?

For example:

{
  "_id" : ObjectId("4c69d19532f73ad544000001"),
  "content" : "blah blah blah",
  "comments" : [
    {"author": "jim", "content":"comment content 1", "date" : "07-24-1995"},
    {"author": "joe", "content":"comment content 2", "date" : "07-24-1996"},
    {"author": "amy", "content":"comment content 3", "date" : "09-10-1999"}
  ]
}
{
  "_id" : ObjectId("4c69d19532f73ad544000002"),
  "content" : "blah blah blah",
  "comments" : [
    {"author": "jim", "content":"comment content 1", "date" : "07-24-1995"},
    {"author": "joe", "content":"comment content 2", "date" : "07-24-1996"},
    {"author": "amy", "content":"comment content 3", "date" : "07-24-1997"}
  ]
}

I want these documents to be ordered in however way I decide and then have comments ordered within my a document by reverse order of date or any other sorting I want.

Is that possible with mongoDB?

5 Answers

Starting in Mongo 4.4, the $function aggregation operator allows applying a custom javascript function to implement behaviour not supported by the MongoDB Query Language.

For instance, in order to sort an array of objects by one of their fields:

// {
//   "content": "blah blah blah",
//   "comments": [
//     { "author": "jim", "content": "comment content 1", "date": "07-24-1995" },
//     { "author": "joe", "content": "comment content 2", "date": "07-24-1996" },
//     { "author": "amy", "content": "comment content 3", "date": "09-10-1999" }
//   ]
// }
db.collection.aggregate(
  { $sort: { content: 1 } }, // or whatever sort order
  { $set:
    { "comments":
      { $function: {
          body: function(comments) {
            return comments.sort((a, b) => a.date < b.date);
          },
          args: ["$comments"],
          lang: "js"
      }}
    }
  }
)
// {
//   "content": "blah blah blah",
//   "comments": [
//     { "author": "amy", "content": "comment content 3", "date": "09-10-1999" },
//     { "author": "joe", "content": "comment content 2", "date": "07-24-1996" },
//     { "author": "jim", "content": "comment content 1", "date": "07-24-1995" }
//   ]
// }

$function takes 3 parameters:

  • body, which is the function to apply, whose parameter is the array to modify.
  • args, which contains the fields from the record that the body function takes as parameter. In our case "$comments".
  • lang, which is the language in which the body function is written. Only js is currently available.

It is possible when use in sequence $unwind > $sort > $group specialty for array,

  • $unwind deconstruct array comments
db.collection.aggregate([
  { $unwind: "$comments" },
  • $addFields to add new field for date, field is in string and we need to convert it to an ISO date for proper sorting, to convert string date to ISO date use $dateFromString
  {
    $addFields: {
      "comments.dateISO": {
        $dateFromString: {
          dateString: "$comments.date",
          format: "%m-%d-%Y"
        }
      }
    }
  },
  • $sort comments.dateISO in descending (-1) order
  { $sort: { "comments.dateISO": -1 } },
  • $group by _id, and re-construct comments array using $push objects and other fields remain using $first
  {
    $group: {
      _id: "$_id",
      comment: { $first: "$comment" },
      comments: { $push: "$comments" }
    }
  }
])

Playground

Related