高级mongodb集合排序
Posted
技术标签:
【中文标题】高级mongodb集合排序【英文标题】:Advanced mongodb collection sorting 【发布时间】:2016-11-11 22:24:17 【问题描述】:我需要 mongodb 中一些高级集合排序的帮助。假设我们有本地数据库并且我们有以下模型:
收集大陆的文件 "_id":1,"name":"欧洲","_id":2,"name":"亚洲","_id":3,"name":"北美", "_id":4,"name":"南美洲","_id":5,"name":"澳大利亚","_id":6,"name":"非洲"
收集有文件的国家 "_id":1,"name":"France","populationInMillions":66,"continent":DBRef("continent",1,"local"),"cities":["name":"Paris ","name":"马赛","name":"图卢兹"],
"_id":2,"name":"Spain","populationInMillions":47,"continent":DBRef("continent",1,"local"),"cities":["name" :"马德里","name":"塞维利亚","name":"瓦伦西亚"],
"_id":3,"name":"China","populationInMillions":1360,"continent":DBRef("continent",2,"local"),"cities":["name" :"北京","名称":"重庆","名称":"上海"],
"_id":4,"name":"Brazil","populationInMillions":200,"continent":DBRef("continent",4,"local"),"cities":["name" :"圣保罗","name":"里约热内卢","name":"萨尔瓦多"]
因此,当我们想通过一些简单的标准(例如 populationInMillions 降序)对国家/地区进行排序时,我们将使用查询: db.country.find().sort( populationInMillions:-1 )
我的问题是,如果我们想按照一些复杂的标准进行排序,比如以下一些 (这个例子中的一些在现实世界中有意义,一些没有意义,但重点是技术解决方案。 我必须在现实世界的项目中应用类似的解决方案。)
对国家/地区进行排序: 1. 按他们所在的大陆的名称(考虑我们没有子对象而是 DBRef)
-
以某种方式,人口数大于 1000 的国家/地区位于其他国家/地区之前
按其所有城市名称中的字符总数(例如法国:巴黎(5 个字符)、马赛(9 个字符)、图卢兹(8 个字符) - 共 22 个字符)
按字母顺序按一个国家的第二个城市的名称(在本例中为法国的马赛,西班牙的塞维利亚等)
如果您对所有或部分问题有答案,请提供帮助。 提前谢谢!
【问题讨论】:
我只是想补充一点,如果您必须使用存储的javascript函数,这些解决方案也是可以接受的。 到目前为止你做了什么?? SO 不是编码服务。 我可以毫不费力地在sql中完成所有这些事情,所以我只想知道我是否也可以在mongo中做到这一点。我放置了这个简化的数据模型,因为我不想让我正在处理的现实世界的例子变得复杂。 【参考方案1】:我绝对同意在 mongodb 中应该避免规范化,并且在上面的示例中,我们应该将大陆作为国家的子对象,以便我们可以轻松地按大陆过滤和排序国家。
在上一个答案中,有一些很好的建议可以通过向数据模型中添加新字段来实现任务。经过一些 mongodb 研究后,我发现了另一种在不真正改变数据模型的情况下实现结果的方法。该解决方案使用聚合。让我们看一下示例 2(以将人口数大于 1000 的国家排在其他国家之前的方式对国家进行排序)。这种类型的解决方案通常可以应用于许多其他自定义排序标准:
db.country.aggregate( [
$project:
_id: "$_id",
name : "$name",
populationInMillions : "$populationInMillions",
cities : "$cities",
populationRank: $cond: if: $gt : [ "$populationInMillions" , 1000 ] , then: 0 , else: 1
,
$sort : 'populationRank' : 1 /*, 'anotherField1' : -1, 'anotherField2' : 1*/ ,
$project : /*We can skip this projection if we don't want to exclude populationRank from the result*/
_id : "$_id",
name : "$name",
populationInMillions : "$populationInMillions",
cities : "$cities"
] );
对于示例 3(按其所有城市名称中的字符总数)我们很遗憾没有 $strlen 函数,但它将在未来的 mongodb 版本中添加。 https://jira.mongodb.org/browse/SERVER-5319 但是如果我们假设我们已经有了 $strlen 函数,这里是示例 3 的有趣解决方案,它也可以为不依赖 strlen 的其他自定义排序标准提供一个思路:
db.country.aggregate(
[ $unwind : "$cities" ,
$group :
_id : "$_id",
name : $max : "$name" ,
populationInMillions : $max : "$populationInMillions" ,
cities : $push : "$cities" ,
citiesCharCount : $sum : $strlen : "$cities.name" ,
$sort : citiesCharCount : 1 ,
$project : /*We can skip this projection if we don't want to exclude citiesCharCount from the result*/
_id : "$_id",
name : "$name",
populationInMillions : "$populationInMillions",
cities : "$cities"
]
);
如果没有 strlen 函数,有基于 mapResuce 和自定义 javascript 函数的解决方案https://docs.mongodb.com/manual/tutorial/map-reduce-examples/
【讨论】:
我认为聚合方法写得很出色,但根据数据集的大小和预期的响应时间,它可能不适合使用。如果没有可以利用像$match
这样的索引的管道,您将有效地读取该集合中的每个文档。随着文档大小的增长,它会变得非常慢。尽管我确实喜欢这样一个事实,即您不再需要使用应用程序端代码来保持重量。干得好。
谢谢!好吧,我是 nosql 数据库的新手,所以我的心态目前是面向关系的,例如,当我在 sql 中遇到这样的情况时,我有一个包含 first_name 和 last_name 列的表人,并且当我想对人进行排序时,例如按他们的姓名首字母,我会查询“select first_name, last_name from person order by substr(first_name, 1, 1) || substr(last_name, 1, 1)” 在persons表中维护另一个列首字母是没有意义的,因为在您需要按首字母排序的少数场景。无论如何我都会跟踪表演。【参考方案2】:
从当前存储文档的方式来看,我认为解决方案要么昂贵要么不可能,因为基于这两个集合的关系添加了更多排序,尤其是在涉及分页时。我建议您将大陆信息放入国家/地区集合中。 MongoDB 被设计为非规范化的,最好利用它。
1 - 按大陆名称对国家/地区进行排序:
-
按所需顺序拉出完整的大陆列表。
使用大陆的
_id
,然后您将提取国家/地区列表,可能使用$in
运算符。
使用HashMap
将两个列表映射在一起
问题:在这种情况下几乎不可能进行分页。效率低下,可能出现重复结果,而且您不太可能自己对国家/地区进行排序,只能对大洲的名称进行排序。
2 - 优先排序超过 1000 的国家/地区优先
我真的不明白你想用这个实现什么。按人口计数排序似乎很好地解决了这个问题。但是,如果您需要类似的东西:
|----------------|
|populationCount |
|----------------|
|2500 |
|2030 |
|2110 |
|2666 |
|1999 |
|800 |
|600 |
|700 |
|----------------|
为此,您可以在您的国家/地区集合中添加一个权重列。对于超过一定数量populationInMillions
(在您的情况下为 1000)的所有国家/地区,将其设置为较高的权重,其余的设置为较低的权重。这样,如果需要,您可以使用db.Countries.sort(weight : -1)
或db.Countries.sort(weight : -1, populationInMillions : -1)
对其进行排序。它会是这样的:
|----------------|------|
|populationCount |weight|
|----------------|------|
|2500 |2 |
|2030 |2 |
|2110 |2 |
|2666 |2 |
|1999 |2 |
|800 |1 |
|600 |1 |
|700 |1 |
|----------------|------|
3 - 按其所有城市名称中的字符总数排序。
我认为 MongoDB 中没有办法即时执行该查询,但由于城市名称不会更改,因此您可以在从国家/地区添加或删除城市时存储总字符数。这样您就可以使用该列进行排序。执行简单,可以索引排序。性能友好。
4 - 按第二个城市名称的字母顺序。
我不知道这意味着什么。有什么例子吗?
p/s :当需要对某些内容进行排序时,请尝试确保条件位于一个集合中以便于查询。
【讨论】:
首先我要感谢您提供的详细而有帮助的答案。让我为您解释一个示例 4 的真实案例。我收集了一系列体育比赛,每场比赛都有一系列不同语言的名称。我想按名称对比赛进行排序,但根据请求,我会动态决定使用哪种语言对比赛进行排序。这是示例编号 4 中的确切场景。 您可以使用db.Sports.find().sort("sportLanguage.n.name" : 1)
对其进行排序,其中 n 是您所需语言的位置。它有一些缺点: 1. 您必须在所有比赛中以相似的顺序输入不同的语言。 2. 排序可能不准确,由于几个可能不可避免的因素,即所有比赛的语言数量不同。我希望为不同语言的不同名称设置单独的列。减少了我的头痛,但我可能是错的。从来没有过这种情况=]
不同语言的单独列是不可接受的,因为总是有可能添加新语言,这会导致数据模型发生变化。所有 mongo 集合都映射到服务层上的类,因此也需要通过添加新字段来更改类。但是我认为,您的第一个解释与存储缺失语言的一些默认值相结合就足够了。然后所有的比赛都将按照一致的顺序进行所有翻译。再次非常感谢您!以上是关于高级mongodb集合排序的主要内容,如果未能解决你的问题,请参考以下文章