Czy MongoDB ' s $in clause guarantee order

Podczas używania MongoDB ' S $in klauzula, czy kolejność zwracanych dokumentów zawsze odpowiada kolejności argumentu array?

Author: styvane, 2014-04-01

10 answers

Jak zauważono, kolejność argumentów w tablicy klauzuli $in nie odzwierciedla kolejności pobierania dokumentów. Będzie to oczywiście kolejność naturalna lub według wybranej kolejności indeksów, jak pokazano.

Jeśli chcesz zachować tę kolejność, masz dwie opcje.

Załóżmy więc, że dopasowywałeś wartości _id w swoich dokumentach tablicą, która zostanie przekazana $in jako [ 4, 2, 8 ].

Podejście z wykorzystaniem Agregat


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 } }

])

Więc byłaby to forma Rozszerzona. Zasadniczo dzieje się tak, że podobnie jak tablica wartości jest przekazywana do $in, konstruujesz również" zagnieżdżoną " instrukcję $cond, aby przetestować wartości i przypisać odpowiednią wagę. Ponieważ ta wartość "waga" odzwierciedla kolejność elementów w tablicy, możesz przekazać tę wartość do etapu sortowania, aby uzyskać wyniki w wymaganej kolejności.

Oczywiście "budujesz" instrukcję pipeline w kodzie, podobne do tego:

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 );

Podejście przy użyciu mapReduce


Oczywiście, jeśli to wszystko wydaje się być dla ciebie bardzo ważne, możesz zrobić to samo używając mapReduce, które wygląda prościej, ale prawdopodobnie będzie działać nieco wolniej.

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;
        }
    }
)

I to w zasadzie polega na tym, że emitowane wartości "klucza "są w" porządku indeksowym", w jaki sposób występują w tablicy wejściowej.


Więc to zasadniczo są twoje sposoby utrzymania kolejności listy wejściowej na $in warunek, w którym masz już tę listę w ustalonej kolejności.

 80
Author: Neil Lunn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-21 08:36:46

Inny sposób przy użyciu zapytania Agregacyjnego tylko dla MongoDB verion >= 3.4 -

Podziękowania należą się temu ładnemu wpisowi na blogu.

Przykładowe dokumenty do pobrania w tej kolejności -

var order = [ "David", "Charlie", "Tess" ];

Zapytanie-

var query = [
             {$match: {name: {$in: order}}},
             {$addFields: {"__order": {$indexOfArray: [order, "$name" ]}}},
             {$sort: {"__order": 1}}
            ];

var result = db.users.aggregate(query);

Kolejny cytat z postu wyjaśniającego użyte operatory agregacji -

Etap "$addFields "jest nowy w 3.4 i pozwala na "$ project " nowe pola do istniejących dokumentów bez wiedzy wszystkie inne istniejące pola. Nowe wyrażenie "$indexOfArray " Zwraca pozycję danego elementu w podanej tablicy.

Zasadniczo operator addFields dodaje nowe pole order do każdego dokumentu, gdy je znajdzie, a to pole order reprezentuje pierwotną kolejność naszej tablicy, którą podaliśmy. Następnie po prostu sortujemy dokumenty na podstawie tego pola.

 40
Author: Jyotman Singh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-10-06 06:52:26

Jeśli nie chcesz używać aggregate, Innym rozwiązaniem jest użycie find, a następnie posortowanie wyników doc po stronie klienta za pomocą array#sort:

Jeśli wartości $in są typami prymitywnymi, takimi jak liczby, możesz użyć podejścia takiego jak:

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);
    });
});

Jeśli wartości $in są nie-prymitywnymi typami, takimi jak ObjectId s, wymagane jest inne podejście, ponieważ indexOf porównuje się przez odniesienie w tym przypadku.

Jeśli używasz Node.js 4.x+, możesz użyć Array#findIndex oraz ObjectID#equals do obsługi to przez zmianę funkcji sort na:

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

Lub z dowolnym węzłem.wersja js z podkreśleniem / 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); });
});
 27
Author: JohnnyHK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-07 13:59:15

Podobnie jak rozwiązanie JonnyHK , możesz zmienić kolejność dokumentów zwróconych z find w swoim kliencie (jeśli twój Klient jest w JavaScript) za pomocą kombinacji map i Array.prototype.find w 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);
        });
    });

});

Kilka uwag:

  • powyższy kod używa sterownika węzła Mongo i Nie Mongoose
  • idArray jest tablicą ObjectId
  • nie testowałem wydajności tej metody vs sort, ale jeśli trzeba manipulować każdy zwracany element (co jest dość powszechne) można to zrobić w wywołaniu zwrotnym map, aby uprościć kod.
 6
Author: tebs1200,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:10

Wiem, że to pytanie jest związane z Mongoose js framework, ale zduplikowany jeden jest ogólny, więc mam nadzieję, że umieszczenie rozwiązania Python (PyMongo) jest w porządku tutaj.

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
 5
Author: Dennis Golomazov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:43

Łatwym sposobem na zamówienie wyniku po zwróceniu tablicy mongo jest utworzenie obiektu z id jako kluczami, a następnie mapowanie nad podanymi _idami, aby zwrócić tablicę, która jest prawidłowo uporządkowana.

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
}
 5
Author: Arne Jenssen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-16 07:22:32

Zawsze? Nigdy. Kolejność jest zawsze taka sama: undefined(prawdopodobnie fizyczna kolejność, w jakiej przechowywane są dokumenty). Chyba, że to rozwiążesz.

 3
Author: freakish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-01 22:10:57

Wiem, że to stary wątek, ale jeśli zwracasz tylko wartość Id w tablicy, być może będziesz musiał wybrać tę składnię. Ponieważ nie mogłem uzyskać wartości indexOf pasującej do formatu 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});
  };

Jak przekonwertować Mongo ObjectId .toString bez włączania wrappera' ObjectId ()' -- tylko wartość?

 1
Author: NoobSter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:24

Możesz zagwarantować zamówienie za pomocą klauzuli $or.

Więc użyj $or: [ _ids.map(_id => ({_id}))] zamiast.

 0
Author: fakenickels,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-14 03:23:10

Jest to rozwiązanie kodu po pobraniu wyników z Mongo. Używanie mapy do przechowywania indeksu, a następnie Zamiana wartości.

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
Author: Prateek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-27 10:55:55