如何在Mongoose中按多个字段对列表中的项进行排序

7

我有一个MongoDB集合中的用户记录(> 10000),可以按照得分降序+时间升序+奖励降序进行排序。如何使用Mongoose根据此排序获取列表中一个用户的排名?假设索引已正确建立。

1个回答

16

在您的排序顺序中计算在此用户之前到来的用户数量。我将从简单(非复合排序)的情况开始,因为复合情况下的查询更加复杂,尽管思想完全相同。

> 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 }

对于这个排序的顺序,一个文档 { "x" : i } 的排名是拥有 i < j 的文档 { "x" : 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

排名将基于0进行计算。同样地,如果您想要在排序{ "x" : 1 }中计算文档{ "x" : i }的排名,您需要计算具有i > j的文档{ "x" : j }的数量。

对于复合排序,相同的过程也适用,但实现起来更棘手,因为复合索引的顺序是词典式的,即对于排序{ "a" : 1, "b" : 1},如果a < ca = cb < d,则(a, b) < (c, d),因此我们需要一个更复杂的查询来表达这个条件。下面是一个复合索引的示例:

> 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 }

为了找到文档{ "x" : i, "y" : j }的排名,您需要找到按顺序{ "x" : 1, "y" : -1 }排列的文档{ "x" : a, "y" : b }的数量,使得(i, j) < (a, b)。根据排序规范,这等效于条件:i < ai = a并且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

最后,在您的三部分复合索引情况下

{ "score" : -1, "time" : 1, "bonus" : -1 }

rank函数将会被使用。

> 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 } }
        ]
    })
}

现在来说有点复杂,但是没有其他方法可以做到。 - qiangbro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接