MySQL Broken Rank - mysql

I have the following query which returns some event details, the number of votes and a rank.
SELECT e.guid,
e.name,
(SELECT COUNT(ev.event_vote_id)
FROM event_vote sv
WHERE ev.event_uid = s.guid) AS votes,
#curRank := #curRank + 1 AS rank
FROM event e, (SELECT #curRank := 0) r
ORDER BY votes DESC
It returns the correct details including votes but the rank is broken.
Actual Result
guid | name | votes | rank
def test2 2 2
abc test1 1 1
ghi test3 0 3
jkl test4 0 4
Expected Result
guid | name | votes | rank
def test2 2 1
abc test1 1 2
ghi test3 0 3
jkl test4 0 4
For some reason test1 has a higher rank than test2.
I assume I need to use a JOIN but i'm unsure on the syntax.

You have to calculate the votes first, then calculate the ranking.
SELECT T.*, #curRank := #curRank + 1 AS rank
FROM ( SELECT e.guid,
e.name,
(SELECT COUNT(ev.event_vote_id)
FROM event_vote sv
WHERE ev.event_uid = s.guid) AS votes
FROM event e
) as T
CROSS JOIN (SELECT #curRank := 0) r
ORDER BY votes DESC
You have wrong result because SELECT section occurs before ORDER section, so you already have a rank but not necessary match the order you get at the end.
Can read more about it here:
Order Of Execution of the SQL query

Related

SQL filter rows without join

I'm always "irk" by unnecessary join. But in this case, I wonder if it's possible to not use join.
This is an example of the table I have:
id | team | score
1 | 1 | 300
2 | 1 | 257
3 | 2 | 127
4 | 2 | 533
5 | 3 | 459
This is what I want:
team | score | id
1 | 300 | 1
2 | 533 | 4
3 | 459 | 5
Doing a query looking like this:
(basically: who's the best player of each team)
SELECT team, MAX(score) AS score, id
FROM my_table
GROUP BY team
But I get something like that:
team | score | id
1 | 300 | 1
2 | 533 | 3
3 | 459 | 5
But it's not the third player that got 533 points, so the result have no consistency.
Is it possible to get truthworthy results without joining the table with itself? How to achieve that?
You can do it without joins by using subquery like this:
SELECT id, team, score
FROM table1 a
WHERE score = (SELECT MAX(score) FROM table1 b WHERE a.team = b.team);
However in big tables this can be very slow as you have to run the whole subquery for every row in your table.
However there's nothing wrong with using join to filter results like this:
SELECT id, team, score FROM table1 a
INNER JOIN (
SELECT MAX(score) score, team
FROM table1
GROUP BY team
) b ON a.score = b.score AND a.team = b.team
Although joining itself is quite expensive, this way you only have to run two actual queries regardless how many rows are in your tables. So in big tables this method can still be hundreds, if not thousands of times faster than the first method with subquery.
You can use variables:
SELECT id, team, score
FROM (
SELECT id, team, score,
#seq := IF(#t = team, #seq,
IF(#t := team, #seq + 1, #seq + 1)) AS seq,
#grp := IF(#t2 = team, #grp + 1,
IF(#t2 := team, 1, 1)) AS grp
FROM mytable
CROSS JOIN (SELECT #seq := 0, #t := 0, #grp := 0, #t2 := 0) AS vars
ORDER BY score DESC) AS t
WHERE seq <= 3 AND grp = 1
Variable #seq is incremented each time a new team is met as the records are being processed in descending score order. Variable #grp is used to enumerate records within each team partition. Records with #grp = 1 are the ones having the greatest score value within the team slice.
Demo here
Unfortantly , MySQL doesn't support window functions like ROW_NUMBER() which could have solved this easily.
There are several ways on doing that:
NOT EXISTS() :
SELECT * FROM YourTable t
WHERE NOT EXISTS(SELECT 1 FROM YourTable s
WHERE t.team = s.team AND s.score > t.score)
NOT IN() :
SELECT * FROM YourTable t
WHERE (t.team,t.score) IN(SELECT s.team,MAX(s.score)
FROM YourTable s
GROUP BY s.team)
A correlated query:
SELECT distinct t.id,t.team,
(SELECT s.score FROM YourTable s
WHERE s.team = t.team
ORDER BY s.score DESC
LIMIT 1)
FROM YourTable t
Or a join which I understand you already have.
EDIT : I take my words back, you can do it with a variable like #GiorgosBetsos solution.
You could do something like this:
SELECT team, score, id
FROM (SELECT *
,RANK() OVER
(PARTITION BY team ORDER BY score DESC) AS Rank
FROM my_table) ranked_result
WHERE Rank = 1;
Some info on Rank functionality: Clicketyclickclick

How to get the rank of a row in mysql query

this my database structure
table : players
id | name | score
1 | Bob | 600
2 | Alex | 1400
3 | John | 800
4 | sara | 2000
I need to select john's row and count what is the john' rank OrderBy score
as you see john is 3rd (800) , sara is 1st (2000), Alex is 2nd (1400) in score ranks
Select #rownum := #rownum + 1 AS rank form players where id=3 OrderBy score
any idea ?
You can do it by a subquery and count the players who has score more than the score of a certian id
Select count(*) as rank
from players
where score > (select score from players where id=3)
But if you want to have other information beside the rank you can do it by
SELECT ranks . *
FROM (
SELECT #rownum := #rownum +1 ‘rank’, p.id, p.score
FROM players p, (SELECT #rownum :=0)r
ORDER BY score DESC
) ranks
WHERE id =3
select rank
from
(
Select id, name, #rownum := #rownum + 1 AS rank
from players
cross join (select #rownum := 0) r
Order By score desc
) tmp
where id = 3
Might be easier to do a self join, where the joined table score is greater (to get the rows with a higher score) and just do a count:-
SELECT COUNT(*)
FROM players a
INNER JOIN players b
ON a.score >= b.score
WHERE a.id = 3
Question is what to do with equal scores.

MySQL find user rank for each category

Let's say I have the following table:
user_id | category_id | points
-------------------------------
1 | 1 | 4
2 | 1 | 2
2 | 1 | 5
1 | 2 | 3
2 | 2 | 2
1 | 3 | 1
2 | 3 | 4
1 | 3 | 8
Could someone please help me to construct a query to return user's rank per category - something like this:
user_id | category_id | total_points | rank
-------------------------------------------
1 | 1 | 4 | 2
1 | 2 | 3 | 1
1 | 3 | 9 | 1
2 | 1 | 7 | 1
2 | 2 | 2 | 2
2 | 3 | 4 | 2
First, you need to get the total points per category. Then you need to enumerate them. In MySQL this is most easily done with variables:
SELECT user_id, category_id, points,
(#rn := if(#cat = category_id, #rn + 1,
if(#cat := category_id, 1, 1)
)
) as rank
FROM (SELECT u.user_id, u.category_id, SUM(u.points) as points
FROM users u
GROUP BY u.user_id, u.category_id
) g cross join
(SELEct #user := -1, #cat := -1, #rn := 0) vars
ORDER BY category_id, points desc;
You want to get the SUM of points for each unique category_id:
SELECT u.user_id, u.category_id, SUM(u.points)
FROM users AS u
GROUP BY uc.category_id
MySQL doesn't have analytic functions like other databases (Oracle, SQL Server) which would be very convenient for returning a result like this.
The first three columns are straightforward, just GROUP BY user_id, category_id and a SUM(points).
Getting the rank column returned is a bit more of a problem. Aside from doing that on the client, if you need to do that in the SQL statement, you could make use of MySQL user-defined variables.
SELECT #rank := IF(#prev_category = r.category_id, #rank+1, 1) AS rank
, #prev_category := r.category_id AS category_id
, r.user_id
, r.total_points
FROM (SELECT #prev_category := NULL, #rank := 1) i
CROSS
JOIN ( SELECT s.category_id, s.user_id, SUM(s.points) AS total_points
FROM users s
GROUP BY s.category_id, s.user_id
ORDER BY s.category_id, total_points DESC
) r
ORDER BY r.category_id, r.total_points DESC, r.user_id DESC
The purpose of the inline view aliased as i is to initialize user defined variables. The inline view aliased as r returns the total_points for each (user_id, category_id).
The "trick" is to compare the category_id value of the previous row with the value of the current row; if they match, we increment the rank by 1. If it's a "new" category, we reset the rank to 1. Note this only works if the rows are ordered by category, and then by total_points descending, so we need the ORDER BY clause. Also note that the order of the expressions in the SELECT list is important; we need to do the comparison of the previous value BEFORE it's overwritten with the current value, so the assignment to #prev_category must follow the conditional test.
Also note that if two users have the same total_points in a category, they will get distinct values for rank... the query above doesn't give the same rank for a tie. (The query could be modified to do that as well, but we'd also need to preserve total_points from the previous row, so we can compare to the current row.
Also note that this syntax is specific to MySQL, and that this is behavior is not guaranteed.
If you need the columns in the particular sequence and/or the rows in a particular order (to get the exact resultset specified), we'd need to wrap the query above as an inline view.
SELECT t.user_id
, t.category_id
, t.total_points
, t.rank
FROM (
SELECT #rank := IF(#prev_category = r.category_id, #rank+1, 1) AS rank
, #prev_category := r.category_id AS category_id
, r.user_id
, r.total_points
FROM (SELECT #prev_categor := NULL, #rank := 1) i
CROSS
JOIN ( SELECT s.category_id, s.user_id, SUM(s.points) AS total_points
FROM users s
GROUP BY s.category_id, s.user_id
ORDER BY s.category_id, total_points DESC
) r
ORDER BY r.category_id, r.total_points DESC, r.user_id DESC
) t
ORDER BY t.user_id, t.category_id
NOTE: I've not setup a SQL Fiddle demonstration. I've given an example query which has only been desk checked.

Find rank of participant in various contests sql

I have a participant table:
userid name
1 John
2 Sam
3 Harry
And there is contest table:
contestid contestname
1 abc
2 def
3 ghi
Score table looks like this:
id contestid userid score
1 1 1 200
2 1 2 300
3 1 3 250
4 2 1 500
5 2 2 400
6 3 2 800
Now, given an userid, I need to find out his rank in all the contest.
The Rank should be based on Contest and Score.
Output should be like this for userid=1:
contestid rank
1 3
2 1
3 Nil
How can I get this output?
1. If you need it Rank by Score and Contest then try this
SET #rank := 0;
SET #prev := NULL;
SELECT contestid,userid,score,Rank FROM
(
SELECT contestid,userid,score,#rank := IF(#prev = contestid, #rank + 1, 1) AS rank,
#prev := contestid
FROM scores ORDER BY contestid, score DESC
) S Where userid = 1
FIDDLE DEMO
2. If you need it Rank by Score then try this
SELECT contestid,userid,score, FIND_IN_SET( score,
(SELECT GROUP_CONCAT(score ORDER BY score DESC) FROM scores)) AS rank
FROM scores
Where UserId = 1
Fiddle Demo
3. If you want UserName and COntest Name then try this
SELECT C.contestname,P.name,S.score, FIND_IN_SET( S.score,
(SELECT GROUP_CONCAT(score ORDER BY score DESC) FROM scores)) AS rank
FROM scores S Join participant P ON P.userid =C.userid
Join contest C ON C.contestid = S.contestid
Where UserId = 1

Selecting the next row in a mysql subselect

I've got a table of data thats ordered by a non-primary key e.g.
id | likes
4 | 6
2 | 5
5 | 2
3 | 2
1 | 2
I need a query to find the row after id #5 which would be id #3.
I've tried using row numbers and written this but it seems really inefficient
select * from (
SELECT l.id,
l.likes,
#curRow := #curRow + 1 AS row_number
FROM sk_posters l
JOIN (SELECT #curRow := 0) r
WHERE active = 'yes'
order by likes desc, id desc)
as mycount where row_number =
(select row_number from (
SELECT l.id,
l.likes,
#curRow := #curRow + 1 AS row_number
FROM sk_posters l
JOIN (SELECT #curRow := 0) r
WHERE active = 'yes'
order by likes desc, id desc)
as mycount
where id=5)+1 limit 1
If there a better, more efficient way to do this?
You can use limit at the end of query like
SELECT * FROM YOUR_TABLE LIMIT 2,1