【问题标题】:Sort document alphabetically (aka natural sort order, sorting for humans) in MongoDB在 MongoDB 中按字母顺序对文档进行排序(又名自然排序顺序,人类排序)
【发布时间】:2016-08-19 08:15:07
【问题描述】:

我想了解如何使用 MongoDB 进行此操作

我有名称为“file1”、“file2”、“file22”、“file11”的文档(名称可以是任何名称,没有特定的模式) 我运行查询以获取按名称排序的所有文档,结果与预期不符。

> db.mydata.find().sort({"name":1});                                                                                                                          
{ "_id" : ObjectId("571e5a787e88d30b20b7857c"), "name" : "file1" }                                                                                            
{ "_id" : ObjectId("571e5a8c7e88d30b20b7857d"), "name" : "file11" }                                                                                           
{ "_id" : ObjectId("571e5a977e88d30b20b7857f"), "name" : "file2" }                                                                                            
{ "_id" : ObjectId("571e5a937e88d30b20b7857e"), "name" : "file22" } 

预期的是(字母/自然顺序)

{ "_id" : ObjectId("571e5a787e88d30b20b7857c"), "name" : "file1" }                                                                                            
{ "_id" : ObjectId("571e5a977e88d30b20b7857f"), "name" : "file2" }                                                                                           
{ "_id" : ObjectId("571e5a8c7e88d30b20b7857d"), "name" : "file11" }
{ "_id" : ObjectId("571e5a937e88d30b20b7857e"), "name" : "file22" }

根据我的发现,还有其他的排序方式,比如使用aggregate + $project$meta: "textScore",但到目前为止我还没有成功。

更新: 这个问题的一个应用:按名称对文件夹/文件进行排序

【问题讨论】:

  • 他们都有file吗??
  • 我不确定file1 < file2 < file11 < ... 是如何被视为字母顺序的?在任何字典中你都找不到A < B < AA
  • 我不会说你想做的是自然秩序。 mongodb 的自然顺序是指文档插入数据库的自然顺序:docs.mongodb.org/v3.0/reference/method/cursor.sort/…
  • @BlazeSahlzen,我真的不想讨论正确的“术语”,已经有一篇文章(blog.codinghorror.com/sorting-for-humans-natural-sort-order)。我更改了问题的标题并添加了“自然排序”标签,因此我们应该专注于问题的解决方案。
  • 如果所有人都同意 MongoDB 不提供此功能,并且必须在 JavaScript 中完成,那么this answer 就是要走的路。

标签: javascript mongodb mongodb-query natural-sort


【解决方案1】:

MongoDB 没有提供开箱即用的方法,但您仍然有两种选择:

第一种是客户端处理,使用Array.prototype.sort方法对数组结果进行排序。

db.mydata.find().toArray().sort((a, b) => { 
    var x = Number(a.name.match(/\d+/g)[0]); 
    var y = Number(b.name.match(/\d+/g)[0]);
    return x === y ? 0 :( x < y ? -1 : 1 );
})

我建议您做的第二件事是使用一个额外的字段来规范您的文档,该字段将“名称”中的数字保存为整数,并使用该值对文档进行排序。这意味着,您需要更新您的文档才能添加该字段,最好的方法是使用$set 更新运算符和"bulk operations" 以获得最大效率。话虽如此,从 MongoDB 服务器版本 3.2 开始,您需要使用 collection.bulkWrite 方法来完成此操作。

var requests = [];

db.mydata.find({}, { "name": 1 } ).forEach(doc => { 
    var fileId = Number(doc.name.match(/\d+/g)[0]); // return number from "name" value
    requests.push({
        "updateOne": { 
            "filter": { "_id": doc._id }, 
            "update": { "$set": { "fileId": fileId } } 
        } 
    }); 
    // Execute per 1000 operations and re-init the requests queue
    if( requests.length === 1000 ) 
        db.mydata.bulkWrite(requests); 
})

// Clean up queues
if (requests.length > 0) 
    db.mydata.bulkWrite(requests);

MongoDB 服务器版本 2.6 开始,您需要使用现已弃用的 Bulk API。

var bulk = db.mydata.initializeUnorderedBulkOp();
var count = 0;

db.collection.find({}, { "name": 1 }).forEach(function(doc) {
    var fileId = Number(doc.name.match(/\d+/g)[0]); 
    bulk.find({"_id": doc._id}).updateOne({ 
        "$set": { "fileId": fileId } 
    });
    count++;
    if (count % 1000 === 0) {
        bulk.execute();
        bulk = db.mydata.initializeUnorderedBulkOp();
    }
})

if (count > 0) 
    bulk.execute();

MongoDB 服务器版本 2.4 开始,您需要一种不同的方法。

db.collection.find({}, { "name": 1 }).forEach(function(doc) {
    var fileId = Number(doc.name.match(/\d+/g)[0]); 
    db.collection.update(
        { "_id": doc._id },
        {"$set": { "fileId": fileId } } 
    );
})

在任何此操作之后,您的文档现在看起来像这样:

{ "_id" : ObjectId("571e5a787e88d30b20b7857c"), "name" : "file1", "fileId" : 1 }
{ "_id" : ObjectId("571e5a8c7e88d30b20b7857d"), "name" : "file11", "fileId" : 11 }
{ "_id" : ObjectId("571e5a977e88d30b20b7857f"), "name" : "file2", "fileId" : 2 }
{ "_id" : ObjectId("571e5a937e88d30b20b7857e"), "name" : "file22", "fileId" : 22 }

现在,您可以使用.sort 方法轻松对文档进行排序。

db.mydata.find({}, { "name": 1 } ).sort( { "fileId": 1 } )

产生以下结果:

{ "_id" : ObjectId("571e5a787e88d30b20b7857c"), "name" : "file1" }
{ "_id" : ObjectId("571e5a977e88d30b20b7857f"), "name" : "file2" }
{ "_id" : ObjectId("571e5a8c7e88d30b20b7857d"), "name" : "file11" }
{ "_id" : ObjectId("571e5a937e88d30b20b7857e"), "name" : "file22" }

【讨论】:

  • 很高兴了解更多关于批量更新操作的信息。如果我们可以找出排序值的模式,那么引入额外的字段是要走的路。但是,在这种情况下,该值可以是任何值,例如普通字符串、IP 地址,... 关于客户端方法,如果我们进行分页,则会出现问题。除非您可以将所有数据返回到客户端(这可能会产生性能问题),否则这种方法不会产生预期的结果。
  • @6220119 正如我所说,规范化绝对是要走的路,因为客户端操作会导致应用程序性能下降。找到模式也应该是一个问题,因为“名称”具有相同类型的值。例如,使用常规字符串,您可以按字符串的长度按升序然后按字母顺序对文档进行排序,这意味着您的额外字段将保存长度。但这绝对是您问题的解决方案。
  • 按长度排序然后按字母顺序不会产生正确的结果。请参阅imgur.com/wPR39Mw 以获得一些灵感。看起来这已成为另一个问题?如何使用 mongoDB 存储字符串值以进行高性能排序操作?
  • 由于poster声明名称可以是任何东西,没有特定的模式,架构修改应该将name拆分为basefileId。然后进行相应的排序:sort({base:1, fileId:1}).
猜你喜欢
  • 1970-01-01
  • 2013-01-07
  • 2011-08-29
  • 2012-09-20
  • 2022-01-06
  • 1970-01-01
  • 1970-01-01
  • 2017-05-22
  • 1970-01-01
相关资源
最近更新 更多