高级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集合排序的主要内容,如果未能解决你的问题,请参考以下文章

MongoDB操作

在MongoDB中永久排序集合

如何在 MongoDB 中按日期对集合进行排序?

mongodb高级查询

MongoDB按自定义字段排序集合

spring mongodb - 排序嵌套集合字段