Kaylit
Kaylit

Reputation: 281

Sort nested array of objects

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?

Upvotes: 28

Views: 41636

Answers (6)

nimrod serok
nimrod serok

Reputation: 16033

Since mongodb version 5.2, you can use $sortArray:

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

Upvotes: 3

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 }
       }
     }
   }
)

Upvotes: -1

Xavier Guihot
Xavier Guihot

Reputation: 61666

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.

Upvotes: 4

Eve Freeman
Eve Freeman

Reputation: 33145

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
}

Upvotes: 36

corvid
corvid

Reputation: 11177

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.

Upvotes: 5

Philipp
Philipp

Reputation: 69663

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

Upvotes: -2

Related Questions