Mongoose find ({_id: idsArray}) сортировать по порядку массива [duplicate]

В моем случае я создал настраиваемую инфраструктуру с целевым назначением для развертывания, установленным в 9.1, но цель развертывания моего приложения была ниже, что поддерживает 8.1. Минимизируйте пользовательскую инфраструктуру. Цель развертывания решила мою проблему.

64
задан styvane 2 November 2015 в 07:18
поделиться

9 ответов

Как уже отмечалось, порядок аргументов в массиве предложения $ 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;
        }
    }
)

И это в основном полагается на испускаемые «ключевые» значения, находящиеся в «индексном порядке» того, как они встречаются во входе array.


Таким образом, это, по сути, ваши способы поддержания порядка списка входных данных в $in, когда у вас уже есть этот список в определенном порядке.

63
ответ дан Robert Rossmann 19 August 2018 в 06:22
поделиться
  • 1
    Отличный ответ. Для тех, кто в этом нуждается, версия coffeescript здесь – Lawrence Jones 25 June 2014 в 19:35
  • 2
    @NeilLunn Я пробовал подход с использованием агрегата, но я получаю id и вес. Вы знаете, как получить сообщения (объект)? – Juanjo Lainez Reche 17 December 2014 в 14:40
  • 3
    @NeilLunn Я действительно сделал (это здесь stackoverflow.com/questions/27525235/… ). Но единственный комментарий был здесь, хотя я проверил это, прежде чем публиковать свой вопрос. Вы можете мне помочь? Спасибо! – Juanjo Lainez Reche 17 December 2014 в 16:20
  • 4
    знаю, что это старо, но я потратил много времени на отладку, почему inputs.indexOf () не соответствовал этому ._id. Если вы просто возвращаете значение идентификатора объекта, вам может потребоваться этот синтаксис: obj.map = function () {for (var i = 0; i & lt; input.length; i ++) {if (this ._id.equals (входы [i])) {var order = i; }} emit (order, {doc: this}); }; – NoobSter 9 August 2016 в 19:40
  • 5
    вы можете использовать & quot; $ addFields & quot; вместо «$ project» если вы хотите, чтобы все исходные поля тоже – Jodo 28 October 2017 в 19:57

Легкий способ упорядочить результат после того, как 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
}
0
ответ дан Arne Jenssen 19 August 2018 в 06:22
поделиться

Я знаю, что это старый поток, но если вы просто возвращаете значение Id в массиве, возможно, вам придется выбрать этот синтаксис. Поскольку мне не показалось, что значение 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 ()' wrapper - только значение?

4
ответ дан Community 19 August 2018 в 06:22
поделиться

Вы можете гарантировать порядок с $ или предложением.

. Вместо этого используйте $or: [ _ids.map(_id => ({_id}))].

0
ответ дан d3t5411 19 August 2018 в 06:22
поделиться

Всегда? Никогда. Порядок всегда один и тот же: неопределенный (возможно, физический порядок хранения документов). Если вы не сортируете его.

2
ответ дан freakish 19 August 2018 в 06:22
поделиться
  • 1
    $natural порядок, который является логичным, а не физическим – Sammaye 2 April 2014 в 22:24

Если вы не хотите использовать aggregate, другое решение - использовать find, а затем сортировать результаты на стороне клиента с помощью 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 сравнивается по ссылке в этом случае.

Если вы используете Node.js 4.x +, вы можете использовать Array#findIndex и ObjectID#equals , чтобы отрегулировать это, изменив функцию sort на:

docs.sort((a, b) => ids.findIndex(id => a._id.equals(id)) - 
                    ids.findIndex(id => b._id.equals(id)));

Или с любой версией Node.js, с подчеркиванием / lodash's 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); });
});
17
ответ дан JohnnyHK 19 August 2018 в 06:22
поделиться
  • 1
    как функция равенства знает, чтобы сравнить свойство id с id 'return a.equals (id);', заставляет ли все свойства возвращаться для этой модели? – lboyel 22 May 2016 в 15:19
  • 2
    @lboyel Я не имел в виду, что это было умным :-), но это сработало, потому что он использовал Document#equals Mongoose для сравнения с полем doc _id. Обновлено, чтобы сделать сравнение _id явным. Спасибо за вопрос. – JohnnyHK 22 May 2016 в 17:59

Другой способ использования запроса агрегирования применим только для версии MongoDB> 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 и позволяет вам «создавать» новые поля для существующих документов, не зная всех других существующих полей. Новое выражение «$ indexOfArray» возвращает позицию определенного элемента в заданном массиве.

В основном оператор addToSet добавляет новое поле order в каждый документ, когда он его находит, и это Поле order представляет собой первоначальный порядок нашего массива, который мы предоставили. Затем мы просто сортируем документы на основе этого поля.

12
ответ дан Jyotman Singh 19 August 2018 в 06:22
поделиться

Это решение кода после того, как результаты получены из 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++
        }
    }
}
0
ответ дан Prateek 19 August 2018 в 06:22
поделиться
4
ответ дан Community 30 October 2018 в 18:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: