Entspricht $in
die Reihenfolge der zurückgegebenen Dokumente bei Verwendung der MongoDB- Klausel immer der Reihenfolge des Array-Arguments?
Entspricht $in
die Reihenfolge der zurückgegebenen Dokumente bei Verwendung der MongoDB- Klausel immer der Reihenfolge des Array-Arguments?
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 ]
.
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 );
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.
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:
Die Phase "$ addFields" ist neu in 3.4 und ermöglicht es Ihnen, neue Felder in vorhandene Dokumente zu "projizieren", ohne alle anderen vorhandenen Felder zu kennen. Der neue Ausdruck "$ indexOfArray" gibt die Position eines bestimmten Elements in einem bestimmten Array zurück.
Grundsätzlich addFields
fügt der Operator order
jedem Dokument ein neues Feld hinzu, wenn er es findet, und dieses order
Feld repräsentiert die ursprüngliche Reihenfolge unseres von uns bereitgestellten Arrays. Dann sortieren wir einfach die Dokumente anhand dieses Feldes.
Wenn Sie nicht verwenden möchten aggregate
, besteht eine andere Lösung darin, find
die Dokumentergebnisse clientseitig zu verwenden und anschließend zu sortieren array#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 wie ObjectId
s handelt, ist indexOf
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
und ObjectID#equals
handhaben, indem Sie die sort
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); });
});
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 aus map
und der Array.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.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
}
Ich 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
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?
Sie können die Bestellung mit $ oder Klausel garantieren.
Verwenden Sie $or: [ _ids.map(_id => ({_id}))]
stattdessen.
$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++
}
}
}