Get the record details and calculate the ranking - mysql

I have a table of exam results. I need to get the record of a specific participant and get his/her ranking too.
for example, the participant with the participant_id 15 must have the ranking 3 amongst the total 4 records. so the result i am looking for would be:
id: 1
exam_id: 3
participant_id: 15
score: 343.23
ranking: 3
I know I can get the record and get the ranking through some PHP code, but I wonder if this is possible with Mysql queries.
I googled but did not really come up with a good solution. Any answer is highly appreciated!

This is the row_number function in postgresql and other databases which unfortunately isn't present in mysql.
This article http://www.mysqltutorial.org/mysql-row_number/ explains how to emulate it in mysql
To adapt the example from it
SET #row_number = 0;
SELECT
(#row_number:=#row_number + 1) AS rank, id, participant_id,exam_id, score
FROM
exams
LIMIT 5;

Following query will give you the required result
select t2.id,t2.exam_id,t2.exam_id,t2.participant_id,t2.score,t2.ranking from
(SELECT t.id,t.exam_id,t.participant_id,t.score,(#num:=#num+1) AS
ranking FROM table1 t cross join (SELECT #num:=0) AS dummy
ORDER BY t.score desc)as t2 where t2.participant_id=15;

You can run a query to create a separate table of results and then add condition to get the rank of required participant.
here is the query you can try
SELECT * FROM (SELECT re.*, #curRow := #curRow + 1 AS rank
FROM results re JOIN (SELECT #curRow := 0) r
WHERE 1 ORDER BY re.`score` DESC) AS tablea
WHERE participant_id=15
here is the result
id exam_id participant_id score rank
1 3 15 343.23 3

Related

using FORCE INDEX to ensure the table is ordered with GROUP BY and ORDER BY before calculating user variables

I am trying to sum the nth highest rows.
I am calculating a cycling league table where 1st fastest rider at an event gets 50 points 2nd fastest 49 points and so on .... there are 10 events over the league but only a rider's 8 best results are used (this means a rider can miss up to 2 events without a catastrophic decent down the leader board)
first i need a table where each rider's results from all events in the league are grouped together and listed in order of highest points, and then a sequential number calculated so i can sum the 8 or less best results.
so i used this table select:
set #r := 0, #rn := 0 ;
SELECT
t.*,
#rn := if(#r = t.id_rider, #rn + 1, 1) as seqnum,
#r := t.id_rider as dummy_rider
from results as t
ORDER BY t.id_rider, t.points desc
where the table results is a view as below:
SELECT
a.id_rider,
b.id_event,
b.race_no,
b.id_race,
b.id_race_type,
b.`position`,
c.id_league,
(51 - b.`position`) AS points
FROM
wp_dtk_start_sheet a
JOIN wp_dtk_position_results b ON a.id_event = b.id_event AND a.race_no = b.race_no
JOIN wp_dtk_league_races c ON b.id_race = c.id_race
WHERE
c.id_league = 1
AND b.`position` IS NOT NULL
this does not work as the seqnum is 1 for all results. if i export the view table into excel and crate a test table with the same columns and data it works ok. i believe what is going wrong is that the table is not being sorted by ORDER BY t.id_rider, t.points desc before running through the variables
this reference: https://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/ states " This technique is pretty much non-deterministic, because it relies on things that you and I don’t get to control directly, such as which indexes MySQL decides to use for grouping"
this reference suggest trying to force the index to use id_rider so i tried:
set #r := 0, #rn := 0 ;
SELECT
a.id_rider,
c.id_league,
(51- b.`position`) as points,
#rn := if(#r = a.id_rider, #rn + 1, 1) as seqnum,
#r := a.id_rider as 'set r'
from wp_dtk_start_sheet as a force index (id_rider)
join wp_dtk_position_results as b on a.id_event = b.id_event and a.race_no = b.race_no
join wp_dtk_league_races as c on b.id_race = c.id_race
where c.id_league = 1 and b.`position` is not null
ORDER BY a.id_rider, points desc
this did not work i got seqnum =1 for all rows as before
my table structure is as below:
table a - wp_dtk_start_sheet
table b - wp_dtk_position_results
table c -wp_dtk_league_races
this stack overlow answer was also very helpfull but also has the same problem with it:
Sum Top 10 Values
can anyone help? perhaps i am going about this all the wrong way?
The solution is much more clear if you use window functions. This allows you to specify the order of rows within each group for purposes of row-numbering.
SELECT t.*
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY id_rider ORDER BY points DESC) AS seqnum
FROM results
) AS t
WHERE t.seqnum <= 8;
Support for window functions in MySQL was introduced in version 8.0, so you might have to upgrade. But it's been part of the MySQL product since 2018.
Bill's answer works brilliantly but I have also combined it into one statement as well, this is the combined select command:
Select
t.id_rider,
sum(points) as total
from
(SELECT
a.id_rider,
c.id_league,
(51- b.`position`) as points,
ROW_NUMBER() OVER (PARTITION BY id_rider ORDER BY points DESC) AS seqnum
from wp_dtk_start_sheet as a
join wp_dtk_position_results as b on a.id_event = b.id_event and a.race_no = b.race_no
join wp_dtk_league_races as c on b.id_race = c.id_race
where c.id_league = 1 and b.`position` is not null ) as t
where seqnum <= 8
group by id_rider
order by total desc

Mysql Ranking Query on 2 columns

Table
id user_id rank_solo lp
1 1 15 45
2 2 7 79
3 3 17 15
How can I sort out a ranking query that sorts on rank_solo ( This ranges from 0 to 28) and if rank_solo = rank_solo , uses lp ( 0-100) to further determine ranking?
(If lp = lp, add a ranking for no tie rankings)
The query should give me the ranking from a certain random user_id. How is this performance wise on 5m+ rows?
So
User_id 1 would have ranking 2
User_id 2 would have ranking 3
User_id 3 would have ranking 1
You can get the ranking using variablesL
select t.*, (#rn := #rn + 1) as ranking
from t cross join
(select #rn := 0) params
order by rank_solo desc, lp;
You can use ORDER BY to sort your query:
SELECT *
FROM `Table`
ORDER BY rank_solo, lp
I'm not sure I quite understand what you're saying. With that many rows, create a query on the fields you're using to do your selects. For example, in MySQL client use:
create index RANKINGS on mytablename(rank_solo,lp,user_id);
Depending on what you use in your query to select the data, you may change the index or add another index with a different field combination. This has improved performance on my tables by a factor of 10 or more.
As for the query, if you're selecting a specific user then could you not just use:
select rank_solo from table where user_id={user id}
If you want the highest ranking individual, you could:
select * from yourtable order by rank_solo,lp limit 1
Remove the limit 1 to list them all.
If I've misunderstood, please comment.
An alternative would be to use a 2nd table.
table2 would have the following fields:
rank (auto_increment)
user_id
rank_solo
lp
With the rank field as auto increment, as it's populated, it will automatically populate with values beginning with "1".
Once the 2nd table is ready, just do this when you want to update the rankings:
delete from table2;
insert into table2 select user_id,rank_solo,lp from table1 order by rank_solo,lp;
It may not be "elegant" but it gets the job done. Plus, if you create an index on both tables, this query would be very quick since the fields are numeric.

mysql ranking based on sum of values

having a mysql table with multiple records belonging many different users like this:
id score
1 , 50
1 , 75
1 , 40
1, 20
2 , 85
2 , 60
2 , 20
i need to get the rank of each id but after finding the sum of their score;
the rank should be the same if the total score for each player is the same.
this gives me the total for each player:
select id,sum(score) as total from table_scores group by id order by total desc;
is it posssible to find the sum like above and use it to rank the players in one query?
Something big missing from the accepted answer. The rank needs to be bumped after a tie. If you've got 2 tied for 3rd place, there is no 4th place.
The following query is an adjustment of the accepted SQL to account for this and reset the rank variable (#r in the query) to match the row value. You can avoid the extra addition in the CASE/WHEN but initializing #row to 1 instead of 0 but then the row value is off by 1 and my OCD won't let that stand even if row number is not valuable.
select
id, total,
CASE WHEN #l=total THEN #r ELSE #r:=#row + 1 END as rank,
#l:=total,
#row:=#row + 1
FROM (
select
id, sum(score) as total
from
table_scores
group by
id
order by
total desc
) totals, (SELECT #r:=0, #row:=0, #l:=NULL) rank;
You can rank rows using variables:
select
id, total,
CASE WHEN #l=total THEN #r ELSE #r:=#r+1 END as rank,
#l:=total
FROM (
select
id, sum(score) as total
from
table_scores
group by
id
order by
total desc
) totals, (SELECT #r:=0, #l:=NULL) rank;
Please see it working here.
i find one more way to this problem... This one is based on JOIN clause
SET #rank = 0;
SELECT t1.id, t1.score, t2.rank
FROM (SELECT id, SUM(score) as score
FROM table_scores GROUP BY id ORDER BY score Desc) AS t1
INNER JOIN
(SELECT x.score, #rank:=#rank + 1 as rank FROM
(SELECT DISTINCT(SUM(score)) AS score
FROM table_scores
GROUP BY id ORDER BY score DESC) AS x) AS t2
ON t1.score = t2.score
Here is SQL Fiddle: http://sqlfiddle.com/#!9/2dcfc/16
P.S. it's interesting to see there is more then one way to solve a problem...

Best 28 result into the sum

I have the following QUERY and I need only the sum of the best 28 results
SELECT id_person, sum(points)
FROM ranking
WHERE id_open>=847 and id_club=2 and id_person!='91'
GROUP BY id_person
ORDER BY sum(points) desc, id_ranking
Each player (id_person), in this serie (id_open=847 or more), in this club (id_club=2) can play about 56 games, but only 28 best result counts for ranking, in other words, I'll despise 28 worst results.
** EDITED QUERY ** (id_ranking isn't necessary in ORDER BY)
SELECT id_person, sum(points)
FROM ranking
WHERE id_open>=847 and id_club=2 and id_person!='91'
GROUP BY id_person
ORDER BY sum(points) desc
If I am understanding correctly...
Your goal: Display players (i.e. id_person) in DESC order based on how many points that person makes in his/her best 28 games.
Here is a query that potentially does that:
SELECT id_person, sum(points) as s
FROM (
SELECT id_person,
points,
#rownum := if(#person = id_person, #rownum + 1, 1) as pointRank,
#person := id_person as dummy
FROM (SELECT id_person, points
FROM ranking
WHERE id_open >= 847 and id_club = 2 and id_person != '91'
ORDER BY id_person, points DESC
) as q1,
(SELECT #rownum := 0) as r,
(SELECT #person := '') as s
) as q2
WHERE pointRank <= 28
GROUP BY id_person
ORDER BY s DESC;
SQL Fiddle (Note: leaves out q1's WHERE clause for convenience)
Subquery q1 explanation:
Filters out rows based on id_open, id_club, and id_person.
Then, orders based on id_person (implicitly ASC) and points (explicitly DESC).
Finally, selects the id_person and points fields.
Note: MySQL Group By's are a little special - aggregates will work, but if selecting non-aggregates, a random row will be selected for the group by, not all rows (MYSQL-Group-By-returns-only-first-row)
Subquery q2 explanation:
Adds in rownum and person variables to keep track of ranking of game. (generate-column-containing-record-index-in-table)
Needs to use the (SELECT #rownum := 0) as r and (SELECT #person := '') as s to set the variables. (These variables could be set outside too.)
if(#person := id_person, #rownum + 1, 1) is necessary to reset the ranking of games per person.
Note: Important to initialize the person variable to an empty string, as opposed to 0. Not too sure why, but if you do not, then it will not reset the rownum variable correctly.
Overall query explanation:
All row numbers (i.e. pointRank) less than or equal to 28 (basically, the 28 best point scores per id_person) are kept.
After this filtering, each id_person will be grouped again.
Finally, the top 28 games' points will be summed up and labeled as s, and these pairs (id_person,s) will be ordered from highest to lowest.
Here is another StackOverflow question with a good article link inside (highly recommended read): (limit-within-group)
Note: I cannot guarantee this will work though, since I do not know what your table looks like. That information would help.

mysql rank from results

Sorry for posting another question about mysql ranking but all questions and answers which I already looked didn't help me....
I have mysql table of user points. User can have more results. My goal is to get max result from user and its rank.
CREATE TABLE results
(`user_id` int, `points` int);
INSERT INTO results VALUES
(1,10),
(2,20),
(3,20),
(4,30),
(4,60),
(5,5),
(1,80);
So upper solution would be:
rank | user_id | points
1 1 80
2 4 60
3 3 20
3 2 20
4 5 5
The following query does the trick:
SET #rank=0;
SET #points=0;
SELECT #rank := IF(#points = a.points, #rank, #rank + 1) AS rank, a.user_id, #points := a.points AS points
FROM (
SELECT user_id, MAX(points) as points
FROM results
GROUP BY user_id
) a
ORDER BY a.points DESC;
I have also created an SQLFiddle of it so you can see that it works: http://sqlfiddle.com/#!2/7ba2f/12
Use a user defined variable to produce the rank when selecting from an aggregated aliased query that calculates the maximum for each user:
select
(#rank := ifnull(#rank, 0) + 1) as rank,
user_id,
points
from (select
user_id,
max(points) as points
from results
group by 1
order by 2 desc) x
FYI, a UDV starts out life as null, hence the ifnull() call.