0

I have query like this,

SELECT * FROM users ORDER BY score

So, the result is like this.

Array
(
    [0] => stdClass Object
        (
            [userid] => 3
            [user] => John Doe
            [score] => 50
        )

    [1] => stdClass Object
        (
            [userid] => 1
            [user] => Mae Smith
            [score] => 38
        )

    [2] => stdClass Object
        (
            [userid] => 2
            [user] => Mark Sam
            [score] => 26
        )
)

But, I want to add a rank using find_in_set query. So the result might be like this. So that the user can view their ranks when they login to their account.

Array
(
    [0] => stdClass Object
        (
            [userid] => 3
            [user] => John Doe
            [score] => 50
            [rank] => 1
        )

    [1] => stdClass Object
        (
            [userid] => 1
            [user] => Mae Smith
            [score] => 38
            [rank] => 2
        )

    [2] => stdClass Object
        (
            [userid] => 2
            [user] => Mark Sam
            [score] => 26
            [rank] => 3
        )
)

I tried this one.

$listOfUser = array();
foreach($users as $user) {
   $listOfUser[] = $user->userid;
}

And used another query

$userid = 2 // => id of loggedin user
SELECT *, find_in_set($userid, $listOfUser) as rank FROM users where userid=$userid ORDER BY score

So, I got this result

Array
(

    [1] => stdClass Object
        (
            [userid] => 2
            [user] => Mark Sam
            [score] => 26
            [rank] => 3
        )
)

Which is somehow correct. But, is there another way of querying that result using only one SQL query and without using foreach loop?

Something like this.

$userid = 2 // => id of loggedin user
SELECT *, find_in_set($userid, (SELECT * FROM users ORDER BY score)) as rank FROM users where userid=$userid ORDER BY score

But I got this error Subquery returns more than 1 row

Codeblooded Saiyan
  • 1,457
  • 4
  • 28
  • 54

3 Answers3

4

If You don't insist on using find_in_set, you can get result with simple join. You ask for list of users (p) and for each user you ask, how many users have better score than him or her (c):

SELECT p.userid, COUNT(c.userid) AS rank
FROM users AS p
LEFT JOIN users AS c ON c.score > p.score
GROUP BY p.userid

This works even if you add other conditions, like WHERE p.userid = 123. If more users have the same score, the ranks would look like 0,1,2,2,2,5,6.

Roman Hocke
  • 4,137
  • 1
  • 20
  • 34
0

In your query, you can add counter, like this:

set @n:=0; 
SELECT @i := @i + 1 AS rank, * FROM users ORDER BY score
Vasyl Zhuryk
  • 1,228
  • 10
  • 23
0

The rank here is relative to the score distribution across all users. I believe you should try something originally proposed in this answer:

SELECT users.*, 
   @rownum := @rownum + 1 as rank
FROM users
CROSS JOIN (select @rownum := 0) r
ORDER BY score DESC

What it does is basically order all users by score, and assign each of them an incremental value "rank". So the top scorer would have a rank of 1, the second scorer would have a rank of 2 etc.

Keep in mind that this solution is not "fair" - each user will have a different rank, even if all users have the same score. If you try to rank users as they do in sports (if two top competitors have the same score, they both take 1st place, and the next best competitor takes 3rd place, not second), you should think of a different solution.

Kleskowy
  • 2,648
  • 1
  • 16
  • 19