This question already has an answer here:
-
Does MongoDB's $in clause guarantee order
10 answers
The MongoDB docs on the $in conditional operator don't say anything about order. If I run a query of the form
db.things.find({'_id': {'$in': id_array}});
what will be the order of the returned results? And is there a way for me to tell MongoDB "I want the results sorted so that they're in the same order as the ids in id_array
?"
Asked for this feature on JIRA:
Quickly got a pretty good response: use $or
instead of $in
c.find( { _id:{ $in:[ 1, 2, 0 ] } } ).toArray()
vs.
c.find( { $or:[ { _id:1 }, { _id:2 }, { _id:0 } ] } ).toArray()
Read the bug report for more info.
Update:
The $or work-around hack no longer works starting with 2.6.x - it was a side effect of implementation which has changed.
I had the same problem and my solution was to create a hash map to do the mapping between my array of ids (my query) and my array of results from MongoDB.
The extra work is to browse the array of results and to insert, for each item, a new key-value pair: the key is the ID and the value is the result object.
Then, when I want to browse my results in the same order as my query was, I can use the hashmap to retrieve the correct object. No sorting, and no fancy Mongo DB option.
In Javascript it would be something like:
//The order you want
var queryIds = [ 8, 5, 3, 7 ];
//The results from MongoDB in an undefined order
var resultsFromMongoDB = [
{_id: 7, data: "d" },
{_id: 8, data: "a" },
{_id: 5, data: "b" },
{_id: 3, data: "c" },
];
//The function to create a hashmap to do the mapping
function createHashOfResults( results ){
var hash = {};
for( var i = 0 ; i < results.length ; i++ ){
var item = results[i];
var id = item._id;
hash[ id ] = item;
}
return hash;
}
//Let's build the hashmap
var hash = createHashOfResults( resultsFromMongoDB );
//Now we can display the results in the order we want
for( var i = 0 ; i < queryIds.length ; i++ ){
var queryId = queryIds[i];
var result = hash[queryId];
console.log( result.data );
}
This will display:
a
b
c
d
@Jason 's answer is the correct one.
About other answers: I wouldn't recommend querying one by one because it can bring serious performance issues.
In addition to @Jason 's answer, it can be optimised using Array.reduce and Array.map methods, like this:
//The order you want
var queryIds = [8, 5, 3, 7];
//The results from MongoDB in an undefined order
var resultsFromMongoDB = [
{_id: 7, data: "d"},
{_id: 8, data: "a"},
{_id: 5, data: "b"},
{_id: 3, data: "c"}
];
var reorderedResults = naturalOrderResults(resultsFromMongoDB, queryIds);
function naturalOrderResults(resultsFromMongoDB, queryIds) {
//Let's build the hashmap
var hashOfResults = resultsFromMongoDB.reduce(function (prev, curr) {
prev[curr._id] = curr;
return prev;
}, {});
return queryIds.map( function(id) { return hashOfResults[id] } );
}
the order of the results isn't mentioned because they won't be ordered in any dependable way. the only way to get them ordered would be to do separate queries client-side for each item in the $in array
If you don't mind using Underscore.js and aren't too worried about scale (IE, you don't mind fetch
-ing rather than working with the cursor) , here's how I maintained order:
var results = db.things.find({'_id': {'$in': id_array}}).fetch();
return _.sortBy(results, function(thing) {
return id_array.indexOf(thing._id);
});
The $or workaround no longer works in 2.6.x, see https://jira.mongodb.org/browse/SERVER-14083. Here's a Ruby workaround implementation: https://gist.github.com/dblock/d5ed835f0147467a6a27