Не в MongoDB $в пункте гарантия того
при использовании MongoDB $in
предложение, Всегда ли порядок возвращаемых документов соответствует порядку аргумента массива?
10 ответов:
как уже отмечалось, порядок аргументов в массиве предложения $in не отражает порядок извлечения документов. Это, конечно, будет естественный порядок или выбранный порядок индекса, как показано на рисунке.
Если вам нужно сохранить этот порядок, то вы в основном есть два варианта.
Итак, предположим, что вы соответствовали по значениям
_id
в ваших документах с массивом, который будет передан в$in
как[ 4, 2, 8 ]
.подход с использованием Aggregate
var list = [ 4, 2, 8 ]; db.collection.aggregate([ // Match the selected documents by "_id" { "$match": { "_id": { "$in": [ 4, 2, 8 ] }, }, // Project a "weight" to each document { "$project": { "weight": { "$cond": [ { "$eq": [ "$_id", 4 ] }, 1, { "$cond": [ { "$eq": [ "$_id", 2 ] }, 2, 3 ]} ]} }}, // Sort the results { "$sort": { "weight": 1 } } ])
так что это будет расширенная форма. В основном здесь происходит то, что так же, как массив значений передается в
$in
вы также строите "вложенный"$cond
оператор для проверки значений и присвоения соответствующего веса. Поскольку это значение "веса" отражает порядок элементов в массиве, вы можете передать это значение на этап сортировки, чтобы получить результаты в требуемом порядок.конечно, вы на самом деле" строите " оператор конвейера в коде, примерно так:
var list = [ 4, 2, 8 ]; var stack = []; for (var i = list.length - 1; i > 0; i--) { var rec = { "$cond": [ { "$eq": [ "$_id", list[i-1] ] }, i ] }; if ( stack.length == 0 ) { rec["$cond"].push( i+1 ); } else { var lval = stack.pop(); rec["$cond"].push( lval ); } stack.push( rec ); } var pipeline = [ { "$match": { "_id": { "$in": list } }}, { "$project": { "weight": stack[0] }}, { "$sort": { "weight": 1 } } ]; db.collection.aggregate( pipeline );
подход с использованием mapReduce
конечно, если все это кажется здоровым для ваших чувств, то вы можете сделать то же самое с помощью mapReduce, который выглядит проще, но, вероятно, будет работать несколько медленнее.
var list = [ 4, 2, 8 ]; db.collection.mapReduce( function () { var order = inputs.indexOf(this._id); emit( order, { doc: this } ); }, function() {}, { "out": { "inline": 1 }, "query": { "_id": { "$in": list } }, "scope": { "inputs": list } , "finalize": function (key, value) { return value.doc; } } )
и это в основном зависит от испускаемых" ключевых "значений, находящихся в "индексном порядке" того, как они происходят во входных данных матрица.
таким образом, это по существу ваши способы поддержания порядка входного списка в
$in
состояние, когда у вас уже есть этот список в определенном порядке.
если вы не хотите использовать
aggregate
, другое решение-использоватьfind
а затем отсортировать результаты doc на стороне клиента с помощьюarray#sort
:если
$in
значения являются примитивными типами, такими как числа вы можете использовать такой подход, как:var ids = [4, 2, 8, 1, 9, 3, 5, 6]; MyModel.find({ _id: { $in: ids } }).exec(function(err, docs) { docs.sort(function(a, b) { // Sort docs by the order of their _id values in ids. return ids.indexOf(a._id) - ids.indexOf(b._id); }); });
если
$in
значения являются непримитивными типами, такими какObjectId
s, другой подход требуется какindexOf
сравнивает по ссылке в этом случае.если вы используете узел.js 4.х+, вы можете использовать
Array#findIndex
иObjectID#equals
чтобы справиться с этим, изменивfindIndex
:docs.sort(function (a, b) { return _.findIndex(ids, function (id) { return a._id.equals(id); }) - _.findIndex(ids, function (id) { return b._id.equals(id); }); });
другой способ использования запроса агрегации, применимого только для MongoDB verion > 3.4 -
кредит идет на это приятно блоге.
пример документов, которые будут извлечены в этом порядке -
var order = [ "David", "Charlie", "Tess" ];
запрос -
var query = [ {$match: {name: {$in: order}}}, {$addFields: {"__order": {$indexOfArray: [order, "$name" ]}}}, {$sort: {"__order": 1}} ]; var result = db.users.aggregate(query);
еще одна цитата из сообщения, объясняющая эти используемые операторы агрегации -
этап "$addFields" является новым в 3.4 и позволяет вам "$project " новые поля для существующих документов, не зная всех других существующих полей. Новое выражение "$indexOfArray " возвращает позицию конкретного элемента в заданном массиве.
в основном
addToSet
оператор добавляет новыйorder
поле для каждого документа, когда он находит его и этоorder
поле представляет исходный порядок нашего массива, который мы предоставили. Затем мы просто сортируем документы на основе этого поля.
аналогично JonnyHK'S решение, вы можете изменить порядок документов, возвращенных из
find
в вашем клиенте (если ваш клиент находится в JavaScript) с комбинациейmap
иArray.prototype.find
функция в EcmaScript 2015:Collection.find({ _id: { $in: idArray } }).toArray(function(err, res) { var orderedResults = idArray.map(function(id) { return res.find(function(document) { return document._id.equals(id); }); }); });
пара замечаний:
- приведенный выше код использует драйвер узла Mongo и не Мангуста
- The
idArray
массивObjectId
- я не проверял производительность этого метода против сортировки, но если вам нужно манипулировать каждым возвращаемым элементом (что довольно часто), вы можете сделать это в
map
обратный вызов для упрощения кода.
всегда? Никогда. Порядок всегда один и тот же: неопределенный (возможно, физический порядок, в котором хранятся документы). Если только вы не разберетесь.
Я знаю, что этот вопрос связан с Мангустом JS framework, но дублировать один является общим, поэтому я надеюсь, что размещение решения Python (PyMongo) здесь отлично.
things = list(db.things.find({'_id': {'$in': id_array}})) things.sort(key=lambda thing: id_array.index(thing['_id'])) # things are now sorted according to id_array order
Я знаю, что это старый поток, но если вы просто возвращаете значение идентификатора в массиве, вам, возможно, придется выбрать этот синтаксис. Поскольку я не мог получить значение indexOf, чтобы соответствовать формату Mongo ObjectId.
obj.map = function() { for(var i = 0; i < inputs.length; i++){ if(this._id.equals(inputs[i])) { var order = i; } } emit(order, {doc: this}); };
как конвертировать Mongo ObjectId .toString без включения оболочки 'ObjectId ()' -- просто значение?
это кодовое решение после получения результатов от Mongo. Использование карты для хранения индекса и последующего обмена значениями.
catDetails := make([]CategoryDetail, 0) err = sess.DB(mdb).C("category"). Find(bson.M{ "_id": bson.M{"$in": path}, "is_active": 1, "name": bson.M{"$ne": ""}, "url.path": bson.M{"$exists": true, "$ne": ""}, }). Select( bson.M{ "is_active": 1, "name": 1, "url.path": 1, }).All(&catDetails) if err != nil{ return } categoryOrderMap := make(map[int]int) for index, v := range catDetails { categoryOrderMap[v.Id] = index } counter := 0 for i := 0; counter < len(categoryOrderMap); i++ { if catId := int(path[i].(float64)); catId > 0 { fmt.Println("cat", catId) if swapIndex, exists := categoryOrderMap[catId]; exists { if counter != swapIndex { catDetails[swapIndex], catDetails[counter] = catDetails[counter], catDetails[swapIndex] categoryOrderMap[catId] = counter categoryOrderMap[catDetails[swapIndex].Id] = swapIndex } counter++ } } }
простой способ упорядочить Результат после того, как mongo возвращает массив, - это сделать объект с идентификатором в качестве ключей, а затем сопоставить заданные _id, чтобы вернуть массив, который правильно упорядочен.
async function batchUsers(Users, keys) { const unorderedUsers = await Users.find({_id: {$in: keys}}).toArray() let obj = {} unorderedUsers.forEach(x => obj[x._id]=x) const ordered = keys.map(key => obj[key]) return ordered }