MongoDB聚合: $sort
聚合的$sort
阶段对所有输入文件进行排序,并按排序顺序返回管道。
语法
{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }
$sort
阶段参数为一个文档,该文档指定了要排序的字段和相应的排序顺序。<sort order>
为下列值之一:
值 | 含义 |
---|---|
1 | 正序(由小到大) |
2 | 逆序(由大到小) |
{ $meta: "textScore" } | 根据textScore 的计算结果按照由大到小排序 |
如果对多个字段进行排序,排序顺序将从左到右进行计算。例如,在上面的表格中,文档首先按<field1>
排序。然后,具有相同<field1>
值的文档再按<field2>
排序。
用法
需要注意:排序最多支持32个键
排序的一致性问题
MongoDB不会按特定顺序对集合中的文档排序,因此,对于有重复值的字段进行排序时,返回的顺序可能会不一样,也就是说排序是不稳定的。如果希望排序顺序一致,至少要在排序字段中包含一个唯一字段,最简单的方法就是排序时加上_id
字段。
如下面的集合restaurant
:
db.restaurants.insertMany( [
{ "_id" : 1, "name" : "Central Park Cafe", "borough" : "Manhattan"},
{ "_id" : 2, "name" : "Rock A Feller Bar and Grill", "borough" : "Queens"},
{ "_id" : 3, "name" : "Empire State Pub", "borough" : "Brooklyn"},
{ "_id" : 4, "name" : "Stan's Pizzaria", "borough" : "Manhattan"},
{ "_id" : 5, "name" : "Jane's Deli", "borough" : "Brooklyn"},
] )
使用$sort
对borough
字段进行排序:
db.restaurants.aggregate(
[
{ $sort : { borough : 1 } }
]
)
在这个例子中,多次排序结果的顺序就可能会有不同,因为borough
字段包含Manhattan
和Brooklyn
的重复值,文档按照borough
的字母顺序返回,在多次执行同一排序时,borough
值重复的文档的顺序就可能不同,下面就是执行两次命令不同的结果:
第一次:
{ "_id" : 3, "name" : "Empire State Pub", "borough" : "Brooklyn" }
{ "_id" : 5, "name" : "Jane's Deli", "borough" : "Brooklyn" }
{ "_id" : 1, "name" : "Central Park Cafe", "borough" : "Manhattan" }
{ "_id" : 4, "name" : "Stan's Pizzaria", "borough" : "Manhattan" }
{ "_id" : 2, "name" : "Rock A Feller Bar and Grill", "borough" : "Queens" }
第二次:
{ "_id" : 5, "name" : "Jane's Deli", "borough" : "Brooklyn" }
{ "_id" : 3, "name" : "Empire State Pub", "borough" : "Brooklyn" }
{ "_id" : 4, "name" : "Stan's Pizzaria", "borough" : "Manhattan" }
{ "_id" : 1, "name" : "Central Park Cafe", "borough" : "Manhattan" }
{ "_id" : 2, "name" : "Rock A Feller Bar and Grill", "borough" : "Queens" }
虽然borough
的值仍按字母顺序排序,但包含重复区值(即Manhattan
和Brooklyn
)的文档的顺序却不一样。
要实现一致的排序,可在排序中添加一个只包含唯一值的字段。以下命令使用
$sort
阶段对borough
和_id
字段进行排序:
db.restaurants.aggregate(
[
{ $sort : { borough : 1, _id: 1 } }
]
)
由于_id
字段是唯一值,因此在多次执行同一排序时,返回的排序顺序总是相同的。
举例
升序/降序排序
对于要排序的一个或多个字段,将排序顺序设为1
或-1
,分别指定升序或降序排序,如下所示:
db.users.aggregate(
[
{ $sort : { age : -1, posts: 1 } }
]
)
该操作将用户集合中的文档按照age
字段降序排序,然后按照posts
字段的值升序排序。
在排序操作中比较不同BSON
类型的值时,MongoDB使用以下从低到高的比较顺序:
- MinKey (internal type)
- Null
- Numbers (ints, longs, doubles, decimals)
- Symbol, String
- Object
- Array
- BinData
- ObjectId
- Boolean
- Date
- Timestamp
- Regular Expression
- MaxKey (internal type)
文本得分元数据排序
对于包含$text
搜索的管道,可以使用{ $meta: "textScore" }
表达式按相关性得分降序排序。在{ <sort-key> }
文档中,将{ $meta: "textScore" }
表达式设置为任意字段名称。查询系统将忽略字段名称。如:
db.users.aggregate(
[
{ $match: { $text: { $search: "operating" } } },
{ $sort: { score: { $meta: "textScore" }, posts: -1 } }
]
)
此操作使用$text
操作符匹配文档,然后首先按"textScore"
元数据降序排序,然后按帖子字段降序排序。查询系统会忽略排序文档中的分数字段名称。在这个流程中,"textScore"
元数据不包含在预测中,也不会作为匹配文档的一部分返回。
$sort
操作和内存
$sort + $limit
内存优化
当$sort
在$limit
之前,且中间没有修改文档数量的阶段时,优化器可以将$limit
合并到$sort
这样,$sort
操作在进行过程中只保留前n
个结果(其中n
是指定的限制),并确保MongoDB只需在内存中存储n
个条目。当allowDiskUse
为true
且n
项超过聚合内存限制时,该优化仍然适用。不同版本之间的优化可能会有变化。
$sort
和内存限制
$sort
受100M内存使用限制,但如果需要,可以将临时文件写入磁盘。从 MongoDB 6.0开始,需要超过100MB内存才能执行的管道阶段默认会将临时文件写入磁盘。在MongoDB早期版本中,要启用此行为,必须传递{ allowDiskUse: true }
。
单个find
和aggregate
命令可以通过以下任一方式覆盖allowDiskUseByDefault
参数:
-
当
allowDiskUseByDefault
设置为false
时,使用{ allowDiskUse: true}
可以把临时文件写入磁盘 -
当
allowDiskUseByDefault
设置为true
时,使用{ allowDiskUse: false}
将禁止把临时文件写入磁盘。
$sort
和性能
如果$sort
用于管道的第一阶段,或仅在$match
阶段之前使用,则可以利用索引的优势。
当使用$sort
时,每个分片会使用可用的索引对文档进行排序,然后,mongos
或其中一个分片执行流式合并排序。