28

I'm looking for a way to sort a nested array of objects.

Here's an example:

{
  answers : [
    { name : 'paul',  state : 'RU' },
    { name : 'steve', state : 'US' }, 
    { name : 'mike',  state : 'DE' }, 
    ...
  ]
}

Suppose now I want to find all the name, of the answers array, but how can I sort them in ascending order?

0

6 Answers 6

36

I would store it in the order you want it back out. Or sort it after you pull it out, on the client side.

If neither of those are possible, you can use the aggregation framework:

> db.test.insert({answers: [
...                 {name: 'paul', state: 'RU'},
...                 {name: 'steve', state: 'US'}, 
...                 {name: 'mike', state: 'DE'}]});
> db.test.insert({answers: [
...                 {name: 'paul', state: 'RU'},
...                 {name: 'steve', state: 'US'}, 
...                 {name: 'xavier', state: 'TX'}]});

db.test.aggregate([
  {$unwind: "$answers"}, 
  {$sort: {"answers.name":1}}, 
  {$group: {_id:"$_id", answers: {$push:"$answers"}}}
]);

produces:

{
  "result" : [
  {
    "_id" : ObjectId("5053b2477d820880c3469364"),
    "answers" : [
      {
        "name" : "paul",
        "state" : "RU"
      },
      {
        "name" : "steve",
        "state" : "US"
      },
      {
        "name" : "xavier",
        "state" : "TX"
      }
    ]
  },
  {
    "_id" : ObjectId("5053af9f7d820880c3469363"),
    "answers" : [
      {
        "name" : "mike",
        "state" : "DE"
      },
      {
        "name" : "paul",
        "state" : "RU"
      },
      {
        "name" : "steve",
        "state" : "US"
      }
    ]
  }
],
  "ok" : 1
}
Sign up to request clarification or add additional context in comments.

Comments

5

This might not be helpful in this context, but I thought I'd add this. You also have the option to sort it on the write which tends to be better for denormalized collections where you don't have to sort in more than one way.

I found this situation in my app when creating a feed on a user.

Meteor.users.helpers({
  'addToFeed': function (gameId, pushData) {
    check(pushData, FeedSchema);
    Meteor.users.update({
      _id: this._id,
      "games.gameId": gameId
    }, {
      $push: {
        "games.$.feed": {
          $each: [pushData],
          $sort: { timestamp: -1 }
        }
      }
    });
  }
});

I found it to be pretty handy because you can then use find() and it will be sorted by your specifications by default.

2 Comments

I'm getting a minimongo error about not being able to set field named $... Any ideas? I think this is really close to what I want
Does anyone know if this i a better/faster approach than keeping the array unsorted and sort in the find query?
4

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:

// {
//   "answers" : [
//     { "name" : "steve",  "state" : "US" },
//     { "name" : "xavier", "state" : "FR" },
//     { "name" : "paul",   "state" : "RU" }
//   ]
// }
db.collection.aggregate(
  { $set:
    { "answers":
      { $function: {
          body: function(answers) { return answers.sort((a, b) => a.name > b.name); },
          args: ["$answers"],
          lang: "js"
      }}
    }
  }
)
// {
//   "answers" : [
//     { "name" : "paul",   "state" : "RU" },
//     { "name" : "steve",  "state" : "US" },
//     { "name" : "xavier", "state" : "FR" }
//   ]
// }

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 "$answers".
  • lang, which is the language in which the body function is written. Only js is currently available.

1 Comment

It might be worthwhile to note that the M0/M2/M5 tiers for MongoDB clusters do not support $function operators in aggregation. Source: docs.atlas.mongodb.com/reference/unsupported-commands
3

Since mongodb version 5.2, you can use $sortArray:

db.engineers.aggregate([
   {$set:
     {answers: {$sortArray: {input: "$answers ", sortBy: { name: 1 } }}}
   }
])

Comments

-1

I have found one solution for this while updating the nested array we can also sort that, after updating we will have an sorted array then we don't need to sort on find

db.students.update(
   { _id: 1 },
   {
     $push: {
       quizzes: {
         $each: [ `enter code here`{ id: 3, score: 8 }, { id: 4, score: 7 }, { id: 5, score: 6 } ],
         $sort: { score: 1 }
       }
     }
   }
)

Comments

-2

After performing a find() you can use sort() on the return value.

db.collection.find({},{"answers.name":1}).sort({"answers.name":1})

The find would extract the name fields of all documents in the collection. The sort would then sort them by name, ascending.

http://www.mongodb.org/display/DOCS/Sorting+and+Natural+Order

2 Comments

You need quotes around your "answers.name" keys. But I don't think your code does what he's looking for...
Your example will list all documents with their _id field and name array, sorted by the first name in each array. The sort() works at the document level and does not reorder arrays within the documents.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.