Entspricht $in
die Reihenfolge der zurückgegebenen Dokumente bei Verwendung der MongoDB- Klausel immer der Reihenfolge des Array-Arguments?
mongodb
mongoose
mapreduce
mongodb-query
aggregation-framework
user2066880
quelle
quelle
Antworten:
Wie bereits erwähnt, spiegelt die Reihenfolge der Argumente im Array einer $ in-Klausel nicht die Reihenfolge wider, in der die Dokumente abgerufen werden. Dies ist natürlich die natürliche Reihenfolge oder die ausgewählte Indexreihenfolge, wie gezeigt.
Wenn Sie diese Reihenfolge beibehalten müssen, haben Sie grundsätzlich zwei Möglichkeiten.
Nehmen wir also an, Sie haben die Werte
_id
in Ihren Dokumenten mit einem Array abgeglichen, das an das$in
as übergeben wird[ 4, 2, 8 ]
.Ansatz mit Aggregat
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 } } ])
Das wäre also die erweiterte Form. Grundsätzlich passiert hier, dass
$in
Sie , sobald das Array von Werten an Sie übergeben wird, auch eine "verschachtelte"$cond
Anweisung erstellen , um die Werte zu testen und ein geeignetes Gewicht zuzuweisen. Da dieser "Gewicht" -Wert die Reihenfolge der Elemente im Array widerspiegelt, können Sie diesen Wert an eine Sortierstufe übergeben, um Ihre Ergebnisse in der erforderlichen Reihenfolge zu erhalten.Natürlich "erstellen" Sie die Pipeline-Anweisung tatsächlich im Code, ähnlich wie folgt:
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 );
Vorgehensweise mit mapReduce
Wenn das alles für Ihre Sensibilität zu schwer zu sein scheint, können Sie dasselbe mit mapReduce tun, das einfacher aussieht, aber wahrscheinlich etwas langsamer läuft.
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; } } )
Und das hängt im Wesentlichen davon ab, dass die ausgegebenen "Schlüssel" -Werte in der "Indexreihenfolge" liegen, wie sie im Eingabearray auftreten.
Dies sind also im Wesentlichen Ihre Möglichkeiten, die Reihenfolge einer Eingabeliste in einem
$in
Zustand zu halten, in dem Sie diese Liste bereits in einer bestimmten Reihenfolge haben.quelle
Eine andere Möglichkeit, die Aggregationsabfrage zu verwenden, gilt nur für die MongoDB-Version> = 3.4 -
Der Kredit geht an diesen schönen Blog-Beitrag .
Beispieldokumente, die in dieser Reihenfolge abgerufen werden sollen -
var order = [ "David", "Charlie", "Tess" ];
Die Abfrage -
var query = [ {$match: {name: {$in: order}}}, {$addFields: {"__order": {$indexOfArray: [order, "$name" ]}}}, {$sort: {"__order": 1}} ]; var result = db.users.aggregate(query);
Ein weiteres Zitat aus dem Beitrag, in dem diese verwendeten Aggregationsoperatoren erläutert werden:
Grundsätzlich
addFields
fügt der Operatororder
jedem Dokument ein neues Feld hinzu, wenn er es findet, und diesesorder
Feld repräsentiert die ursprüngliche Reihenfolge unseres von uns bereitgestellten Arrays. Dann sortieren wir einfach die Dokumente anhand dieses Feldes.quelle
Wenn Sie nicht verwenden möchten
aggregate
, besteht eine andere Lösung darin,find
die Dokumentergebnisse clientseitig zu verwenden und anschließend zu sortierenarray#sort
:Wenn die
$in
Werte primitive Typen wie Zahlen sind, können Sie einen Ansatz wie den folgenden verwenden: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); }); });
Wenn es sich bei den
$in
Werten um nicht-primitive Typen wieObjectId
s handelt, istindexOf
in diesem Fall ein anderer Ansatz erforderlich, der durch Bezugnahme verglichen wird.Wenn Sie Node.js 4.x + verwenden, können Sie dies verwenden
Array#findIndex
undObjectID#equals
handhaben, indem Sie diesort
Funktion in Folgendes ändern :docs.sort((a, b) => ids.findIndex(id => a._id.equals(id)) - ids.findIndex(id => b._id.equals(id)));
Oder mit einer beliebigen Node.js-Version mit Unterstrich / lodashs
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); }); });
quelle
Document#equals
, um es mit dem_id
Feld des Docs zu vergleichen . Aktualisiert, um den_id
Vergleich explizit zu machen . Danke für die Frage.Ähnlich wie bei der Lösung von JonnyHK können Sie die von
find
Ihrem Client zurückgegebenen Dokumente (wenn Ihr Client JavaScript verwendet) mit einer Kombination ausmap
und derArray.prototype.find
Funktion in EcmaScript 2015 neu anordnen: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); }); }); });
Ein paar Anmerkungen:
idArray
ist ein Array vonObjectId
map
Rückruf tun , um Ihren Code zu vereinfachen.quelle
Eine einfache Möglichkeit, das Ergebnis zu ordnen, nachdem Mongo das Array zurückgegeben hat, besteht darin, ein Objekt mit der ID als Schlüssel zu erstellen und dann die angegebenen _id's zuzuordnen, um ein Array zurückzugeben, das korrekt geordnet ist.
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 }
quelle
Immer? Noch nie. Die Reihenfolge ist immer dieselbe: undefiniert (wahrscheinlich die physische Reihenfolge, in der Dokumente gespeichert werden). Es sei denn, Sie sortieren es.
quelle
$natural
Normalerweise bestellen, was eher logisch als physisch istIch weiß, dass diese Frage mit dem Mongoose JS-Framework zusammenhängt, aber das duplizierte ist generisch. Daher hoffe ich, dass das Posten einer Python-Lösung (PyMongo) hier in Ordnung ist.
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
quelle
Ich weiß, dass dies ein alter Thread ist, aber wenn Sie nur den Wert der ID im Array zurückgeben, müssen Sie sich möglicherweise für diese Syntax entscheiden. Da ich scheinbar keinen indexOf-Wert bekommen konnte, der mit einem mongo ObjectId-Format übereinstimmt.
obj.map = function() { for(var i = 0; i < inputs.length; i++){ if(this._id.equals(inputs[i])) { var order = i; } } emit(order, {doc: this}); };
Wie konvertiere ich mongo ObjectId .toString ohne den Wrapper 'ObjectId ()' - nur den Wert?
quelle
Sie können die Bestellung mit $ oder Klausel garantieren.
Verwenden Sie
$or: [ _ids.map(_id => ({_id}))]
stattdessen.quelle
$or
Problemumgehung hat seit Version 2.6 nicht mehr funktioniert .Dies ist eine Codelösung, nachdem die Ergebnisse aus Mongo abgerufen wurden. Verwenden einer Karte zum Speichern des Index und anschließendes Austauschen von Werten.
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++ } } }
quelle