Count the number of users that come in front of that user in your sort order. I will start with the case of simple (non-compound sorting), because the query in the complex case is more complex, although the idea is exactly the same.
> db.test.drop() > for (var i = 0; i < 10; i++) db.test.insert({ "x" : i }) > db.test.find({ }, { "_id" : 0 }).sort({ "x" : -1 }).limit(5) { "x" : 9 } { "x" : 8 } { "x" : 7 } { "x" : 6 } { "x" : 5 }
For this order, the ranking of the document { "x" : i } is the number of documents { "x" : j } with i < j
> var rank = function(id) { var i = db.test.findOne({ "_id" : id }).x return db.test.count({ "x" : { "$gt" : i } }) } > var id = db.test.findOne({ "x" : 5 }).id > rank(id) 4
Ratings will be based on 0. Similarly, if you want to calculate the rank for a document { "x" : i } in sorting { "x" : 1 } , you can count the number of documents { "x" : j } with i > j .
For compound sorting, the same procedure is performed, but it is more difficult to implement, because the order in the composite index is lexicographic, i.e. for sorting { "a" : 1, "b" : 1} , (a, b) < (c, d) if a < c or a = c and b < d , therefore, to express this condition, we need a more complex inquiry. Here is an example for a composite index:
> db.test.drop() > for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { db.test.insert({ "x" : i, "y" : j }) } } > db.test.find({}, { "_id" : 0 }).sort({ "x" : 1, "y" : -1 }) { "x" : 0, "y" : 2 } { "x" : 0, "y" : 1 } { "x" : 0, "y" : 0 } { "x" : 1, "y" : 2 } { "x" : 1, "y" : 1 } { "x" : 1, "y" : 0 } { "x" : 2, "y" : 2 } { "x" : 2, "y" : 1 } { "x" : 2, "y" : 0 }
To find the rank for the document { "x" : i, "y" : j } , you need to find the number of documents { "x" : a, "y" : b } in the order { "x" : 1, "y" : -1 } so that (i, j) < (a, b) . Given the sort specification, this is equivalent to the condition i < a or i = a and j > b :
> var rank = function(id) { var doc = db.test.findOne(id) var i = doc.x var j = doc.y return db.test.count({ "$or" : [ { "x" : { "$lt" : i } }, { "x" : i, "y" : { "$gt" : j } } ] }) } > id = db.test.findOne({ "x" : 1, "y" : 1 })._id > rank(id) 4
Finally, in your case, a three-part composite index
{ "score" : -1, "time" : 1, "bonus" : -1 }
rank function will be
> var rank = function(id) { var doc = db.test.findOne(id) var score = doc.score var time = doc.time var bonus = doc.bonus return db.test.count({ "$or" : [ { "score" : { "$gt" : score } }, { "score" : score, "time" : { "$lt" : time } }, { "score" : score, "time" : time, "bonus" : { "$gt" : bonus } } ] }) }