Сортировка документа по алфавиту (он же естественный порядок сортировки, сортировка для людей) в MongoDB

Я хотел бы узнать, как это сделать с помощью 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", но пока этого не удалось.

UPDATE: Применение этой проблемы: сортировать папки/файлы по именам Проводник Windows, папки, отсортированные по имени

Ответы

Ответ 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 и "массовые операции" для максимальной эффективности. При этом с сервера 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" }