MongoDB $sort

In MongoDB, the $sort aggregation pipeline stage sorts all input documents and returns them to the pipeline in sorted order.

Syntax

The syntax goes like this:

{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }

Where <sort order> can be 1 for ascending, -1 for descending, or { $meta: "textScore" } to sort by the computed textScore metadata in descending order.

Sample Data

Suppose we have a collection called pets with the following documents:

{ "_id" : 1, "name" : "Wag", "type" : "Dog", "weight" : 20 }
{ "_id" : 2, "name" : "Bark", "type" : "Dog", "weight" : 10 }
{ "_id" : 3, "name" : "Meow", "type" : "Cat", "weight" : 7 }
{ "_id" : 4, "name" : "Scratch", "type" : "Cat", "weight" : 8 }
{ "_id" : 5, "name" : "Bruce", "type" : "Kangaroo", "weight" : 100 }
{ "_id" : 6, "name" : "Hop", "type" : "Kangaroo", "weight" : 130 }
{ "_id" : 7, "name" : "Punch", "type" : "Kangaroo", "weight" : 200 }
{ "_id" : 8, "name" : "Snap", "type" : "Cat", "weight" : 12 }
{ "_id" : 9, "name" : "Ruff", "type" : "Dog", "weight" : 30 }

Sort in Ascending Order

To sort in ascending order, we use 1 for the sort order.

Below is an example of a query that uses the $sort operator to sort that collection by the weight field in ascending order.

db.pets.aggregate([
    { $sort: { weight: 1 } } 
])

Result:

{ "_id" : 3, "name" : "Meow", "type" : "Cat", "weight" : 7 }
{ "_id" : 4, "name" : "Scratch", "type" : "Cat", "weight" : 8 }
{ "_id" : 2, "name" : "Bark", "type" : "Dog", "weight" : 10 }
{ "_id" : 8, "name" : "Snap", "type" : "Cat", "weight" : 12 }
{ "_id" : 1, "name" : "Wag", "type" : "Dog", "weight" : 20 }
{ "_id" : 9, "name" : "Ruff", "type" : "Dog", "weight" : 30 }
{ "_id" : 5, "name" : "Bruce", "type" : "Kangaroo", "weight" : 100 }
{ "_id" : 6, "name" : "Hop", "type" : "Kangaroo", "weight" : 130 }
{ "_id" : 7, "name" : "Punch", "type" : "Kangaroo", "weight" : 200 }

Sort in Descending Order

To sort in descending order, we use -1 for the sort order.

db.pets.aggregate([
    { $sort: { weight: -1 } } 
])

Result:

{ "_id" : 7, "name" : "Punch", "type" : "Kangaroo", "weight" : 200 }
{ "_id" : 6, "name" : "Hop", "type" : "Kangaroo", "weight" : 130 }
{ "_id" : 5, "name" : "Bruce", "type" : "Kangaroo", "weight" : 100 }
{ "_id" : 9, "name" : "Ruff", "type" : "Dog", "weight" : 30 }
{ "_id" : 1, "name" : "Wag", "type" : "Dog", "weight" : 20 }
{ "_id" : 8, "name" : "Snap", "type" : "Cat", "weight" : 12 }
{ "_id" : 2, "name" : "Bark", "type" : "Dog", "weight" : 10 }
{ "_id" : 4, "name" : "Scratch", "type" : "Cat", "weight" : 8 }
{ "_id" : 3, "name" : "Meow", "type" : "Cat", "weight" : 7 }

Sort by Multiple Fields

To sort by more than one field, separate each field/sort order combo with a comma.

Example

db.pets.aggregate([
    { $sort: { type: 1, weight: -1, _id: 1 } }
])

Result:

{ "_id" : 8, "name" : "Snap", "type" : "Cat", "weight" : 12 }
{ "_id" : 4, "name" : "Scratch", "type" : "Cat", "weight" : 8 }
{ "_id" : 3, "name" : "Meow", "type" : "Cat", "weight" : 7 }
{ "_id" : 9, "name" : "Ruff", "type" : "Dog", "weight" : 30 }
{ "_id" : 1, "name" : "Wag", "type" : "Dog", "weight" : 20 }
{ "_id" : 2, "name" : "Bark", "type" : "Dog", "weight" : 10 }
{ "_id" : 7, "name" : "Punch", "type" : "Kangaroo", "weight" : 200 }
{ "_id" : 6, "name" : "Hop", "type" : "Kangaroo", "weight" : 130 }
{ "_id" : 5, "name" : "Bruce", "type" : "Kangaroo", "weight" : 100 }

In this example, we sorted by the type field in ascending order first, then by the weight field in descending order, then by the _id field in ascending order.

This means that, if there are multiple pets of the same type, those pets are sorted by their weight in descending order. If there are multiple pets with both the same type and weight, then those pets are sorted by the _id field in ascending order. If we hadn’t included the _id field in the sorting process, then those pets of the same type and weight could appear in any order. This is true each time we run the query. Without having a sort field on a unique field (such as the _id field), it would be entirely possible (even probable) that the results would come back in a different order each time the query was run.

Sorting Different Types

When comparing values of different BSON types, MongoDB uses the following comparison order, from lowest to highest:

  1. MinKey (internal type)
  2. Null
  3. Numbers (ints, longs, doubles, decimals)
  4. Symbol, String
  5. Object
  6. Array
  7. BinData
  8. ObjectId
  9. Boolean
  10. Date
  11. Timestamp
  12. Regular Expression
  13. MaxKey (internal type)

Suppose we have a collection called posts with the following documents:

{
	"_id" : 1,
	"title" : "Web",
	"body" : "Create a website with these three easy steps...",
	"date" : "2021-01-01T00:00:00.000Z"
}
{
	"_id" : 2,
	"title" : "Animals",
	"body" : "Animals are funny things...",
	"date" : ISODate("2020-01-01T00:00:00Z")
}
{
	"_id" : 3,
	"title" : "Oceans",
	"body" : "Oceans are wide and vast...",
	"date" : ISODate("2021-01-01T00:00:00Z")
}

Notice that the first date field contains a date string, whereas the other two documents use a Date object.

Also notice that the date string contains exactly the same date as document 3, and this date is a later date than the date in document 2.

Let’s apply $sort to the date fields of those documents:

db.posts.aggregate([
    { $sort: { date: 1 } } 
]).pretty()

Result:

{
	"_id" : 1,
	"title" : "Web",
	"body" : "Create a website with these three easy steps...",
	"date" : "2021-01-01T00:00:00.000Z"
}
{
	"_id" : 2,
	"title" : "Animals",
	"body" : "Animals are funny things...",
	"date" : ISODate("2020-01-01T00:00:00Z")
}
{
	"_id" : 3,
	"title" : "Oceans",
	"body" : "Oceans are wide and vast...",
	"date" : ISODate("2021-01-01T00:00:00Z")
}

In this case we sorted in ascending order, which means earlier dates should come first. However, our first document contains a date string instead of a Date object, and so it came first – even though its date is later than the date in document 2.

Here it is again, but in descending order:

db.posts.aggregate([
    { $sort: { date: -1 } } 
]).pretty()

Result:

{
	"_id" : 3,
	"title" : "Oceans",
	"body" : "Oceans are wide and vast...",
	"date" : ISODate("2021-01-01T00:00:00Z")
}
{
	"_id" : 2,
	"title" : "Animals",
	"body" : "Animals are funny things...",
	"date" : ISODate("2020-01-01T00:00:00Z")
}
{
	"_id" : 1,
	"title" : "Web",
	"body" : "Create a website with these three easy steps...",
	"date" : "2021-01-01T00:00:00.000Z"
}

Once again, the date ordering is out of whack, due to the different data types.

Text Score Metadata Sort

You can use the { $meta: "textScore" } argument to sort by descending relevance score when using $text searches.

Example

db.posts.aggregate(
   [
     { $match: { $text: { $search: "funny" } } },
     { $sort: { score: { $meta: "textScore" }, title: -1 } }
   ]
).pretty()

Result:

{
	"_id" : 2,
	"title" : "Animals",
	"body" : "Animals are funny things...",
	"date" : ISODate("2020-01-01T00:00:00Z")
}

In this case, only one document matched our query.

In this example, we sorted by { $meta: "textScore" }, then by title in descending order. We used score as an arbitrary field name, but this is ignored by the query system.

Doing $text searches like this requires that we’ve created a text index. If not, an IndexNotFound error will be returned.

Sorting Grouped Results

Going back to our pets collection, we can use the $sort stage after a $group stage to sort a group of documents by the number of values in a particular field.

db.pets.aggregate([
    {
      $match: { weight: { $lt: 30 } }
    },
    {
      $group: { _id: "$type", count: { $sum: 1 } }
    },
     { 
      $sort : { count : -1 } 
    }
])

Result:

{ "_id" : "Cat", "count" : 3 }
{ "_id" : "Dog", "count" : 2 }

However, you might be better off using the $sortByCount operator in such cases.

More Information

See the MongoDB documentation for more information.