45

I've been looking for a while now and can't seem to sort an inner array and keep that in the doc that I'm currently working with.

{
    "service": {
        "apps": {
            "updates": [
              {
                "n" : 1
                "date": ISODate("2012-03-10T16:15:00Z")
              },
              {
                "n" : 2
                "date": ISODate("2012-01-10T16:15:00Z")
              },
              {
                "n" : 5
                "date": ISODate("2012-07-10T16:15:00Z")
              }
            ]
        }
     }
 }

So I want to keep the item to be returned as the service, but have my updates array sorted. So far with the shell I have:

db.servers.aggregate(
        {$unwind:'$service'},
        {$project:{'service.apps':1}},
        {$unwind:'$service.apps'}, 
        {$project: {'service.apps.updates':1}}, 
        {$sort:{'service.apps.updates.date':1}});

Anyone think they can help on this?

arawind
  • 52
  • 10
Ricky Hartmann
  • 891
  • 1
  • 7
  • 20
  • Does this answer your question? [Sort nested array of objects](https://stackoverflow.com/questions/12432727/sort-nested-array-of-objects) – nimrod serok May 30 '22 at 16:09

3 Answers3

69

You can do this by $unwinding the updates array, sorting the resulting docs by date, and then $grouping them back together on _id using the sorted order.

db.servers.aggregate(
    {$unwind: '$service.apps.updates'}, 
    {$sort: {'service.apps.updates.date': 1}}, 
    {$group: {_id: '$_id', 'updates': {$push: '$service.apps.updates'}}}, 
    {$project: {'service.apps.updates': '$updates'}})
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
16

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:

// {
//   "service" : { "apps" : { "updates" : [
//     { "n" : 1, "date" : ISODate("2012-03-10T16:15:00Z") },
//     { "n" : 2, "date" : ISODate("2012-01-10T16:15:00Z") },
//     { "n" : 5, "date" : ISODate("2012-07-10T16:15:00Z") }
//   ]}}
// }
db.collection.aggregate(
  { $set: {
    { "service.apps.updates":
      { $function: {
          body: function(updates) {
            updates.sort((a, b) => a.date - b.date);
            return updates;
          },
          args: ["$service.apps.updates"],
          lang: "js"
      }}
    }
  }
)
// {
//   "service" : { "apps" : { "updates" : [
//     { "n" : 2, "date" : ISODate("2012-01-10T16:15:00Z") },
//     { "n" : 1, "date" : ISODate("2012-03-10T16:15:00Z") },
//     { "n" : 5, "date" : ISODate("2012-07-10T16:15:00Z") }
//   ]}}
// }

This modifies the array in place, without having to apply a combination of expensive $unwind, $sort and $group stages.

$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 "$service.apps.updates".
  • lang, which is the language in which the body function is written. Only js is currently available.
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
12

Starting in Mongo 5.2, it's the exact use case for the new $sortArray aggregation operator:

// {
//   service: { apps: { updates: [
//     { n: 1, date: ISODate("2012-03-10") },
//     { n: 2, date: ISODate("2012-01-10") },
//     { n: 5, date: ISODate("2012-07-10") }
//   ]}}
// }
db.collection.aggregate([
  { $set: {
    "service.apps.updates": {
      $sortArray: {
        input: "$service.apps.updates",
        sortBy: { date: 1 }
      }
    }
  }}
])
// {
//   service: { apps: { updates: [
//     { n: 2, date: ISODate("2012-01-10") },
//     { n: 1, date: ISODate("2012-03-10") },
//     { n: 5, date: ISODate("2012-07-10") }
//   ]}}
// }

This:

  • sorts ($sortArray) the service.apps.updates array (input: "$service.apps.updates")
  • by applying a sort on dates (sortBy: { date: 1 })
  • without having to apply a combination of expensive $unwind, $sort and $group stages
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190