8

HI,

I actually posted similar (or same?) question yesterday, but I thought I need to post a new question since I have short, but clear question.

I have the following table.

id  point
1   30
2   30
3   29
4   27
5   28
6   26

what I want:

  1. get all the users order by rank. user #1 and #2 should have 1 as their rank value because they both have 30 points

  2. I want to query a rank by user id. I like to get 1 as the result of my rank when I query user #1 and #2 because both of them have 30 points

Added on: 3/18

I tried Logan's query, but got the following result

id point   rank
1   30  1
2   30  1
3   29  3
4   27  5
5   28  4
6   26  6
ajcw
  • 23,604
  • 6
  • 30
  • 47
Moon
  • 22,195
  • 68
  • 188
  • 269

6 Answers6

5

Just count how many people have more points then them.

select count(1) from users 
where point > (select point from users where id = 2) group by point

This will give you the number of people that have more points for the given user. So for user 1 and user 2 the result will be 0 (zero) meaning they are first.

RDL
  • 7,865
  • 3
  • 29
  • 32
5

The subquery approach that you have seen recommended will scale quadratically. http://www.xaprb.com/blog/2006/12/02/how-to-number-rows-in-mysql/ shows a much more efficient approach with user variables. Here is an untested adaptation to your problem:

@points := -1; // Should be an impossible value.
@num := 0;

SELECT id
  , points
  , @num := if(@points = points, @num, @num + 1) as point_rank
  , @points := points as dummy
FROM `users`
ORDER BY points desc, id asc;
btilly
  • 43,296
  • 3
  • 59
  • 88
  • I tried to add where statment to query a specified row, but I always get 1 as point_rank. below is my query – Moon Mar 18 '11 at 20:14
  • SELECT *, vote_ratio , @num := if( @vote_ratio = vote_ratio, @num , @num +1 ) AS rank FROM challenge_photos where img_status = 1 and img_id = 65 ORDER BY vote_ratio DESC"; – Moon Mar 18 '11 at 20:15
  • @btilly //forgot mention your id. – Moon Mar 18 '11 at 21:47
  • @Moon: What you need to do is make what I wrote into a view, and then select from the view. If you need to access the view repeatedly, save it into a table (which can be temporary if you want) and then access that. – btilly Mar 19 '11 at 01:40
  • @btilly // thanks again!! but the problem is...it would be a high traffic website. I don't think it is a good idea to query all the records and make a view every time someone click a photo to see its rank.. – Moon Mar 19 '11 at 04:04
  • @Moon: In that case store a table that precalculates the result, and refresh it regularly with a cron. – btilly Mar 20 '11 at 05:52
  • The first two users are supposed to have the same rank. The num + 1 actually needs to be incremented like num + number_of_ranks_skipped. See my solution... – malhal Aug 04 '12 at 21:38
2

The OP would like to have rank numbers skipped if their previously were duplicate points with the same rank. E.g. below see how 2 is skipped because rank 1 appears twice.

id  point  rank
1   30     1
2   30     1
3   29     3
4   27     4
5   28     5
6   26     6

This can be achieved by modifying btilly's code as follows:

set @points := -1; // Should be an impossible value.
set @num := 0;
set @c := 1;
SELECT id
  , points
  , @num := if(@points = points, @num, @num + @c) as point_rank
  , @c := if(@points = points, @c+1, 1) as dummy
  , @points := points as dummy2
FROM `users`
ORDER BY points desc, id asc;
malhal
  • 26,330
  • 7
  • 115
  • 133
2

When I needed to do something similar, I created a view that looked like this:

CREATE VIEW rankings_view 
AS 
SELECT id
,      point
,      (select count(1) 
          from points b
         where  b.point > a.point) +1 as rank
FROM points as a;

This assumes that the original table was named points, obviously. Then you can get the rank of any id, or the id corresponding to any rank, by querying the view.

EDIT

If you want to count the number of distinct point values above each point value instead of the number of entries with point values above the current point value, you can do something like:

CREATE VIEW rankings_view2
AS 
SELECT id
,      point
,      (SELECT COUNT(1) +1 AS rank 
          FROM ( SELECT DISTINCT point
                   FROM points b
                  WHERE   b.point >a.point ))
FROM points AS a;

NOTE

Some of the other solutions presented definitely perform better than this one. They're mysql specific, so I can't really use them for what I'm doing. My application has, at most, 128 entities to rank, so this works well enough for me. If you might have tons of rows, though, you might want to look at using one of the other solutions presented here or limiting the scope of the ranking.

lo5an
  • 76
  • 4
  • why create the view? The select statement works (though I'd add `ORDER BY rank ASC`). Nice solution. – Dawson Mar 18 '11 at 05:02
  • @Dawson I like to keep most of my SQL in the database itself in views and stored procedures. – lo5an Mar 18 '11 at 15:10
  • @Logan // added the query result above – Moon Mar 18 '11 at 17:07
  • @Logan // is it possible to get the value 2 for id #2 instead of 3? I know that 3 makes sense since there are two rank #1, but I just wonder if I can set it as #2 instead of #3. – Moon Mar 18 '11 at 17:14
  • @Moon That shouldn't be a problem. You need to count the number of distinct point values above each point value instead of the number of rows. I'll edit my post. – lo5an Mar 18 '11 at 17:42
  • @Logan // wow...I appreciate your answer! it works perfectly now. – Moon Mar 18 '11 at 20:12
  • With duplicate rows i get: `View's SELECT contains a subquery in the FROM clause` – Ricky Odin Matthews Oct 15 '14 at 13:39
0

In mysql 8 you can use window function like:

SELECT
  id,
  score,
  rank() over (order by amount) as ranking
FROM
  SomeTable

If you need to select only one row, use a subquery:

SELECT
  id
  score,
  ranking
FROM (
  SELECT
    id,
    score,
    rank() over (order by score) as ranking
  FROM
    SomeTable
) t
WHERE
  id = ?
golopot
  • 10,726
  • 6
  • 37
  • 51
-1
SET @rank = 0, @prev_val = NULL;
SELECT id, @rank := IF(@prev_val=points,@rank,@rank+1) AS rank,
@prev_val := points AS points FROM users ORDER BY points DESC, id asc;

Table:users

Dawson
  • 7,567
  • 1
  • 26
  • 25
  • The first two users are supposed to have the same rank. – btilly Mar 18 '11 at 03:30
  • Yep. Sorry about that. Thanks for the lashing. Answer revamped – Dawson Mar 18 '11 at 04:11
  • When revamping your answer, you *could* have acknowledged that it is the same idea as the answer I posted before you revamped yours. – btilly Mar 18 '11 at 05:57
  • Sorry. It really was an oversight. My solution: tested, and verified to OP. I think I like @Logan's better any way. It's more direct, and easier to read - it's simpler. – Dawson Mar 18 '11 at 06:57
  • @Dawson: It is true that @Logan has a simpler solution. But if you have 100,000 rows in your table, performance will kill you. – btilly Mar 18 '11 at 07:09
  • @btilly - indeed. Kind of a crux with the forum. You never know how involved to make an answer based on a lot of the posts. The OP may just have a 100 row table, or this is a homework assignment on syntax/technique. – Dawson Mar 18 '11 at 07:29