When using MongoDB\'s $in clause, does the order of the returned documents always correspond to the order of the array argument?
An easy way to order the result after mongo returns the array is to make an object with id as keys and then map over the given _id's to return an array that is correctly ordered.
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
}
Always? Never. The order is always the same: undefined (probably the physical order in which documents are stored). Unless you sort it.
I know this question is related to Mongoose JS framework, but the duplicated one is generic, so I hope posting a Python (PyMongo) solution is fine here.
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
Another way using the Aggregation query only applicable for MongoDB verion >= 3.4 -
The credit goes to this nice blog post.
Example documents to be fetched in this order -
var order = [ "David", "Charlie", "Tess" ];
The query -
var query = [
{$match: {name: {$in: order}}},
{$addFields: {"__order": {$indexOfArray: [order, "$name" ]}}},
{$sort: {"__order": 1}}
];
var result = db.users.aggregate(query);
Another quote from the post explaining these aggregation operators used -
The "$addFields" stage is new in 3.4 and it allows you to "$project" new fields to existing documents without knowing all the other existing fields. The new "$indexOfArray" expression returns position of particular element in a given array.
Basically the addFields
operator appends a new order
field to every document when it finds it and this order
field represents the original order of our array we provided. Then we simply sort the documents based on this field.
This is a code solution after the results are retrieved from Mongo. Using a map to store index and then swapping values.
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++
}
}
}
I know this is an old thread, but if you're just returning the value of the Id in the array, you may have to opt for this syntax. As I could not seem to get indexOf value to match with a mongo ObjectId format.
obj.map = function() {
for(var i = 0; i < inputs.length; i++){
if(this._id.equals(inputs[i])) {
var order = i;
}
}
emit(order, {doc: this});
};
How to convert mongo ObjectId .toString without including 'ObjectId()' wrapper -- just the Value?