Ranking positions using SQL - mysql

Trying to rank positions but instead of giving (1,2,3,3,5..) when there is a tie in the 3rd position it gives (1,2,3,4,5...).. Help please
below is the code.. thanks
.............................................................................
SELECT t.*
FROM (SELECT #curRank := IF(#prev= #cur, #curRank, #curRank + 1 ) AS classPosition,student_id, #prev:=#cur, #cur:=SUM(total_marks)
FROM (SELECT m.*
FROM marks m
WHERE classform_name = ? AND term = ? AND academic_year = ? GROUP BY student_id
ORDER BY SUM(total_marks) DESC
) n CROSS JOIN
(SELECT #curRank := 0, #prev:=NULL, #cur:=NULL ) q GROUP BY student_id
) t
WHERE student_id = ?
.............................................................................

You don't need a RANK function. You can always rank rows by joining the table to itself, in which case you get to choose how the order (and rank) is determined.

I've mixing session/# variables and aggregation can be a bit unreliable, but you could simply try changing the order of your select expressions to
student_id, #cur:=SUM(total_marks), #curRank := IF(#prev= #cur, #curRank, #curRank + 1 ) AS classPosition, #prev:=#cur
If this doesn't help, I'd suggest separating your aggregation and ranking. Actually, you're already summing in the subquery, so I am not quite sure why you're not just including the sum in the results from n.
SELECT * FROM (
SELECT #curRank := IF(#prev= #cur, #curRank, #curRank + 1 ) AS classPosition, student_id, #prev:=#cur, #cur:=overall
FROM (SELECT m.*, SUM(total_marks) AS overall
FROM marks m
WHERE classform_name = ? AND term = ? AND academic_year = ?
GROUP BY student_id
ORDER BY overall DESC
) AS n
CROSS JOIN (SELECT #curRank := 0, #prev:=NULL, #cur:=NULL ) AS q
) AS completeRankings
WHERE student_id = ?
Actually, your original query should've had other issues anyway. n would have only included a single random total_marks value for each student_id; making the outer SUM kind of pointless.
Edit - This should allow position progression to "skip" later positions according to ties:
SELECT #curRank := #curRank + 1 AS counter, #prevRank := IF(#prev=#cur, #prevRank, #curRank) AS classPosition
... to omit counter from the final results, you'll have to expand out the * explicitly.

Ranks are a real pain in MySQL.
If you want the rank for a single student with these conditions, I would recommend:
SELECT 1 + COUNT(*)
FROM marks m
WHERE m.classform_name = ? AND m.term = ? AND m.academic_year = ? AND
m.total_marks >= (SELECT m2.total_marks
FROM marks m2
WHERE m2.classform_name = m.classform_name AND
m2.term = m.term
m2.academic_year = m.academic_year
m2.student_id = ?
);;

Related

ranking of data based on multiple columns in mysql with using rank function

i am very new to mysql and tried to rank top records based on two fields
sharing you the current script and output along with the desired output
Current Script :
select u_rank, c_rank,u_name,c_name,
#curRank := #curRank + 1 AS rank
from (
SELECT u_rank, c_rank,u_name,c_name
from abc
) a,
(
select #curRank := 0
) r
order by c_rank,u_rank
Current OutpuT
MY DESIRED OUTPUT IS
Please help
If I am decifering your question correctly, you are wanting to rank the c_ranks ascending or each u_name and then order by that rank then the u_rank. After that you need to get a rank for all records in that order. I'm not sure that the way I just worded that made any sense... but the following should get that result for you:
SET #rank1 = 0;
SET #rank2 = 0;
SET #u_name = '';
SELECT u_rank, c_rank, u_name, c_name, #rank2 := #rank2 + 1 AS rank
FROM (
SELECT u_rank,
c_rank,
c_name,
#rank1 := (#rank1 + 1 - IF(#u_name = u_name, 0, #rank1)) AS rank1,
#u_name := u_name AS u_name
FROM abc
ORDER BY u_name, c_rank
) foo
ORDER BY rank1, u_rank;

MySQL SUM TOP 2 RECORDS FOR EACH CATEGORY

I have a table for students scores, am trying to sum top 2 marks for all student for a particular category.I have search for similar post but have not gotten correct answer
I have tried summing the marks but am only getting result for two students instead of all students and it does not give me correct value.
SELECT SUM(marks) as totalmarks,stdid
FROM (( select marks,stdid
from finalresult
where `subjectcategory` = 1
AND `classId`='3' AND `year`='2018'
AND `term`='2' AND `type`='23'
order by marks desc
LIMIT 2 ))t1
GROUP BY stdid
An auxiliary subquery might be used for iteration
SELECT
stdid, marks
FROM
(
SELECT stdid, marks,
#rn := IF(#iter = stdid, #rn + 1, 1) AS rn,
#iter := stdid
FROM finalresult
JOIN (SELECT #iter := NULL, #rn := 0) AS q_iter
WHERE `subjectcategory` = 1
AND `classId`='3'
AND `year`='2018'
AND `term`='2'
AND `type`='23'
ORDER BY stdid, marks DESC
) AS T1
WHERE rn <= 2
this solution ignores ties and takes only two for each Student ID.
Demo
In MySQL 8+, you would do:
SELECT stdid, SUM(marks) as totalmarks
FROM (SELECT fr.*,
ROW_NUMBER() OVER (PARTITION BY stdid ORDER BY marks DESC) as seqnm
FROM finalresult fr
WHERE subjectcategory = 1 AND
classId = 3 AND
year = 2018 AND
term = 2 AND
type = 23
) fr
WHERE seqnum <= 2
GROUP BY stdid;
Note that I removed the single quotes. Things that look like numbers probably are. And you should not mix type -- put the quotes back if the values really are stored as strings.
In earlier versions, probably the simplest method is to use variables, but you have to be very careful about them. MySQL does not guarantee the order of evaluation of variables in SELECT, so you cannot assign a variable in one expression and use it in another.
A complicated expression solves this. Also, it is best to sort in a subquery (the latest versions of MySQL 5+ require this):
SELECT stdid, SUM(marks) as totalmarks
FROM (SELECT fr.*,
(#rn := IF(#s = stdid, #rn + 1,
IF(#s := stdid, 1, 1)
)
) as seqnum
FROM (SELECT fr.*
FROM finalresult fr
WHERE subjectcategory = 1 AND
classId = 3 AND
year = 2018 AND
term = 2 AND
type = 23
ORDER BY stdid, marks DESC
) fr CROSS JOIN
(SELECT #s = '', #rn := 0) params
WHERE seqnum <= 2
GROUP BY stdid;

MySQL get rank from particular row ID

I have list of hospitals under that there are average ratings already calculated. Now I wanted to calculate rank for list of hospitals according to their average ratings from following query
SELECT name,
hospitalID,
currentAvgRating,
#curRank := #curRank + 1 AS rank
FROM hospitals h, (SELECT #curRank := 0) r
ORDER BY currentAvgRating DESC
Now above query works when I want to see all hospitals from table but when I apply WHERE clause like below then result is wrong since with this it takes row position.
SELECT name,
hospitalID,
currentAvgRating,
#curRank := #curRank + 1 AS rank
FROM hospitals h, (SELECT #curRank := 0) r where hospitalID = '453085'
ORDER BY currentAvgRating DESC
Is there any way to get correct result when we apply where clause?
If you proceed what you just found out, logically ("when there is only 1 listitem, it cannot be ordered") - you will come to the conclusion that you NEED to select ALL rows. But nothing wrong with that, you can pack them into a subselect (which isnt even an expensive one) and apply the WHERE to that:
SELECT * FROM (
SELECT name,
hospitalID,
currentAvgRating,
#curRank := #curRank + 1 AS rank
FROM hospitals h, (SELECT #curRank := 0) r
ORDER BY currentAvgRating DESC
) toplist
WHERE toplist.hospitalID = 453085
Wrap in a subquery.
SELECT * FROM (
SELECT name,
hospitalID,
currentAvgRating,
#curRank := #curRank + 1 AS rank
FROM hospitals h, (SELECT #curRank := 0) r
ORDER BY currentAvgRating DESC
)
WHERE hospitalID = '453085'

MariaDB/MySQL RANK() implementation

I am working on migration from MS SQL Server to MariaDB 10.0. I have query which use RANK() PARTITION BY. I already created some kind of RANK() implementation on my table but it's not working properly.
The original query was:
RANK() OVER (PARTITION BY visits.id_partner ORDER BY visits.updated_at DESC) AS rank
My implementation for MariaDB/MySQL:
SELECT
...,
(
CASE visits.id_partner
WHEN #currId THEN
#curRow := #curRow + 1
ELSE
#curRow := 1 AND #currId := visits.id_partner
END
) AS rank
FROM
records rec
JOIN visits ON visits.id = rec.id_visit,
(
SELECT
#curRank := 0,
#currId := NULL
) r
WHERE
...
ORDER BY visits.id_partner ASC, visits.updated_at DESC
I want to select row ranked by id_partner order by updated_at field. When id_partner is same as on row before RANK should increase by 1. When is different than before, it should reset to 1.
But my query is not working at all. I have still rank 1 on all rows. Can you help me find mistake?
Thank you for help!
It is tricky to use variables in MySQL/MariaDB. A variable should only be used and assigned in one statement (as you do correctly). However, AND can short-circuit variable assignment.
I use a construct like this for ROW_NUMBER(). RANK() is actually a bit of a pain . . . DENSE_RANK() and ROW_NUMBER() are simpler. However, this seems to be the code that you are aiming for:
SELECT ...,
(#rn := if(#currId = visits.id_partner, #rn + 1,
if(#currId := visits.id_partner, 1, 1)
)
) as rank
FROM records rec JOIN
visits
ON visits.id = rec.id_visit CROSS JOIN
(SELECT #rn := 0, #currId := NULL) params
WHERE
...
ORDER BY visits.id_partner ASC, visits.updated_at DESC;
EDIT:
In MySQL (and presumably in MariaDB), sometimes variables don't work quite right unless you use a subquery. So, try this:
SELECT . . .,
(#rn := if(#currId = visits.id_partner, #rn + 1,
if(#currId := visits.id_partner, 1, 1)
)
) as rank
FROM (SELECT ...
FROM records rec JOIN
visits
ON visits.id = rec.id_visit
WHERE
...
ORDER BY visits.id_partner ASC, visits.updated_at DESC
) t CROSS JOIN
(SELECT #rn := 0, #currId := NULL) params;

MySQL Ranking ID with Ties Based on Column Returns Error

I have a 1 table database with (in a simplified form) the following fields:
user_id(INT), ref_id(INT), points(INT), pointsgiven(BOOL/TINY_INT)
I want a query that returns the RANK of the user_id I specify based on points, given that pointsgiven is true. The kicker is, I need ties included. I can get a result set for ALL ranks if I want with the following query
SELECT
user_id, ref_id, points, pointsgiven,
CASE
WHEN #points = COALESCE(points, 0) THEN #rownum
ELSE #rownum := #rownum + 1
END AS rank,
#points := COALESCE(points, 0)
FROM users CT
JOIN
(
SELECT #rownum := 0, #points := NULL
) r
WHERE pointsgiven=TRUE ORDER BY points DESC
So, based on that, I thought I could just use that as a subquery to get a certain user_id as follows:
select * from
(
SELECT
user_id, ref_id, points, pointsgiven,
CASE
WHEN #points = COALESCE(points, 0) THEN #rownum
ELSE #rownum := #rownum + 1
END AS rank,
#points := COALESCE(points, 0)
FROM users CT
JOIN
(
SELECT #rownum := 0, #points := NULL
) r
WHERE pointsgiven=TRUE ORDER BY points DESC
) as derived WHERE user_id = 15
But this returns [BLOB - 1 B] as the rank on the correct user_id. What am I doing wrong here?
I have no idea why your query isn't working. For a single user id, though, you can use a correlated subquery:
select user_id, ref_id, points, pointsgiven,
coalesce((select count(distinct user_id)
from users u2
where u2.pointsgiven=TRUE and
u2.points > u.points
) + 1, 1) as rank
from users u
where user_id = 15;
An index on users(pointsgiven, points, user_id) should be used by the query.
To look at just one ranking, this might even be faster than your method.