2

I have a collection of documents of the format:

{
  "user": { "username": "string" }
  "score": 123
}

I want to find a user's rank within the collection.

collection.aggregate([
    { $sort: { score: -1 } }, // sort by score descending
    { 
        $project: {
            score: '$score'
            rank: {
                $indexOfArray: [ 
                    '$', // I don't know what to place here
                    { 'user.username': 'myUser' }
                ]
            }
        }
    }
]);

How do I treat the collection as an array?

I'm looking for this result:

[
  {
    "score": 123,
    "rank": 1
  }
]
2
  • What's the json you are expecting as a result? Commented Aug 27, 2019 at 18:37
  • @Caconde updated Commented Aug 27, 2019 at 18:38

1 Answer 1

3

We can achieve this by performing a self lookup. The following query can get us the expected output:

db.collection.aggregate([
    {
        $match:{
            "user.username":"B"
        }
    },
    {
        $lookup:{
            "from":"my_collection_name",
            "let":{
                "score":"$score"
            },
            "pipeline":[
                {
                    $match:{
                        $expr:{
                            $gt:["$score","$$score"]
                        }
                    }
                },
                {
                    $group:{
                        "_id":null,
                        "above":{
                            $sum:1
                        }
                    }
                }
            ],
            "as":"selfLookup"
        }
    },
    {
        $unwind:{
            "path":"$selfLookup",
            "preserveNullAndEmptyArrays":true
        }
    },
    {
        $project:{
            "_id":0,
            "score":1,
            "rank":{
                $cond:[
                    {
                        $eq:["$selfLookup.above",null]
                    },
                    1,
                    {
                        $sum:["$selfLookup.above",1]
                    }
                ]
            }
        }
    }
]).pretty()

Data set:

{
    "_id" : ObjectId("5d657d2d7d0ab652c42315f3"),
    "user" : {
        "username" : "A"
    },
    "score" : 123
}
{
    "_id" : ObjectId("5d657d2d7d0ab652c42315f4"),
    "user" : {
        "username" : "B"
    },
    "score" : 122
}
{
    "_id" : ObjectId("5d657d2d7d0ab652c42315f5"),
    "user" : {
        "username" : "C"
    },
    "score" : 121
}

Output:

{ "score" : 122, "rank" : 2 }

Explanation: We are calculating the count of users which has scores more than the score of the searched user('B' in this case). If no records are found, rank is 1, else the rank would be count + 1.

Sign up to request clarification or add additional context in comments.

6 Comments

When I run this, the first aggregation stage (match) reduces the collection to 1 item, so every rank is 1.
No. The first aggregation stage gets us the record which has the searched user name. From that record, we also get a score. That score is then used to lookup in the same collection (2nd aggregation stage).
I see. I'm still getting rank 1 no matter which username I type in. When I add foo: '$selfLookup.above' to my $project, it doesn't appear to exist.
Can you please add some sample data?
You are changing the collection name in lookup right? in from field?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.