I have a table as below
ID_STUDENT | ID_CLASS | GRADE
-----------------------------
1 | 1 | 90
1 | 2 | 80
2 | 1 | 99
3 | 1 | 80
4 | 1 | 70
5 | 2 | 78
6 | 2 | 90
6 | 3 | 50
7 | 3 | 90
I need to then group, sort and order them and update the assigned rank :
ID_STUDENT | ID_CLASS | GRADE | RANK
------------------------------------
2 | 1 | 99 | 1
1 | 1 | 90 | 2
3 | 1 | 80 | 3
4 | 1 | 70 | 4
6 | 2 | 90 | 1
1 | 2 | 80 | 2
5 | 2 | 78 | 3
7 | 3 | 90 | 1
6 | 3 | 50 | 2
Can anyone help me to achieve this in mysql .
Below query will sorts the results group by ID_CLASS and in the descending order of GRADE columns.
Query
select `ID_STUDENT`, `ID_CLASS`, `GRADE`, (
case `ID_CLASS`
when #curA then #curRow := #curRow + 1
else #curRow := 1 and #curA := `ID_CLASS` end
) as `rank`
from `your_table_name` g,
(select #curRow := 0, #curA := '') r
order by `ID_CLASS`, `GRADE` desc;
We can use session variables here to simulate the rank. Note carefully that you are really after the rank, and not just the row number. The reason for this is that two or more students in a given class could be tied with the same grade. Your sample data did not include such an edge case, but the case is present in your problem. I chose to use the dense rank here, which means that if a class had give students with the following grades:
student | grade | dense rank
1 | 90 | 1
2 | 90 | 1
3 | 90 | 1
4 | 80 | 2
5 | 65 | 3
then a three-way tie for the highest grade would mean that all three students would be first, with the next highest student coming in at second. The alternative would be to use the rank, in which case the next highest student would have rank 4.
SET #dr = 1;
SET #id_class = NULL;
SET #grade = NULL;
SELECT
ID_STUDENT,
#dr:=CASE WHEN (#id_class = ID_CLASS AND #grade <> GRADE) THEN #dr + 1
WHEN (#id_class = ID_CLASS AND #grade = GRADE) THEN #dr
ELSE 1 END AS dr,
#id_class:=ID_CLASS AS ID_CLASS,
#grade:=GRADE AS GRADE
FROM yourTable
ORDER BY
ID_CLASS,
GRADE DESC;
Output:
Demo here:
Rextester
SELECT ID_STUDENT,ID_CLASS,GRADE,RANK
FROM
(
SELECT ID_STUDENT,ID_CLASS,GRADE,CASE WHEN #ID_CLASS=ID_CLASS THEN #RANK:= #RANK+1 ELSE #RANK :=1 END RANK,#ID_CLASS:=ID_CLASS
FROM STUDENT_TABLE,(SELECT #RANK :=0,#ID_CLASS:=1)Z ORDER BY ID_CLASS ASC,GRADE DESC
)ZZ;
You can try above query.
Here is SQLFiddle Demo
i have seem u r question and find out ans as per u r need.
i think help below query to find data proper format.
SELECT * FROM `temp_u` GROUP BY `ID_STUDENT` ORDER by `RANK` ASC
UPDATE youre_tbl t2
SET t2 .RANK= (SELECT *
FROM youre_tbl t
ORDER BY t.GRADE)
FROM t2
WHERE t2.ID = t.ID
You can get ordered result from this.
Related
Say I have a table like so:
+---+-------+------+---------------------+
|id | level |score | timestamp |
+---+-------+------+---------------------+
| 4 | 1 | 70 | 2021-01-14 21:50:38 |
| 3 | 1 | 90 | 2021-01-12 15:38:0 |
| 1 | 1 | 20 | 2021-01-14 13:10:12 |
| 5 | 1 | 50 | 2021-01-13 12:32:11 |
| 7 | 1 | 50 | 2021-01-14 17:15:20 |
| 8 | 1 | 55 | 2021-01-14 09:20:00 |
| 10| 2 | 99 | 2021-01-15 10:50:38 |
| 2 | 1 | 45 | 2021-01-15 10:50:38 |
+---+-------+------+---------------------+
What I want to do is show 5 of these rows in a table (in html), with a certain row (e.g. where id=5) in the middle and have the two rows above and below it (in the correct order). Also where level=1. This will be like a score board but only showing the user's score with the two above and two below.
So because scores can be the same, the timestamp column will also need to be used - so if two scores are equal, then the first person to get the score is shown above the other person.
E.g. say the user is id=5, I want to show
+---+-------+------+---------------------+
|id | level |score | timestamp |
+---+-------+------+---------------------+
| 4 | 1 | 70 | 2021-01-14 21:50:38 |
| 8 | 1 | 55 | 2021-01-14 09:20:00 |
| 5 | 1 | 50 | 2021-01-13 12:32:11 |
| 7 | 1 | 50 | 2021-01-14 17:15:20 |
| 2 | 1 | 45 | 2021-01-15 10:50:38 |
| 1 | 1 | 20 | 2021-01-14 13:10:12 |
+---+-------+------+---------------------+
Note that id=7 is below id=5
I am wondering does anyone know a way of doing this?
I have tried this below but it is not outputting what I need (it is outputting where level_id=2 and id=5, and the other rows are not in order)
((SELECT b.* FROM table a JOIN table b ON b.score > a.score OR (b.score = a.score AND b.timestamp < a.timestamp)
WHERE a.level_id = 1 AND a.id = 5 ORDER BY score ASC, timestamp DESC LIMIT 3)
UNION ALL
(SELECT b.* FROM table a JOIN table b ON b.score < a.score OR (b.score = a.score AND b.timestamp > a.timestamp)
WHERE a.level_id = 1 AND a.id = 5 ORDER BY score DESC, timestamp ASC LIMIT 2))
order by score
If it is easier to output all rows in the table, say where level = 1, so it is a full score board.. and then do the getting a certain row and two above and below it using PHP I'd also like to know please :) ! (possibly thinking this may keep the SQL simpler)?
You can use cte and inner join as follows:
With cte as
(select t.*,
dense_rank() over (order by score) as dr
from your_table t)
Select c.*
From cte c join cte cu on c.dr between cu.dr - 2 and cu.dr + 2
Where cu.id = 5
Ordwr by c.dr, c.timestamp
I would suggest window functions:
select t.*
from (select t.*,
max(case when id = 7 then score_rank end) over () as id_rank
from (select t.*,
dense_rank() over (order by score) as score_rank
from t
where level = 1
) t
) t
where score_rank between id_rank - 2 and id_rank + 2;
Note: This returns 5 distinct score values, which may result in more rows depending on duplicates.
Here is a db<>fiddle.
EDIT:
If you want exactly 5 rows using the timestamp, then:
select t.*
from (select t.*,
max(case when id = 7 then score_rank end) over () as id_rank
from (select t.*,
dense_rank() over (order by score, timestamp) as score_rank
from t
where level = 1
) t
) t
where score_rank between id_rank - 2 and id_rank + 2
order by score;
Note: This still treats equivalent timestamps as the same, but they seem to be unique in your data.
I have a table like so (after doing a query on it to order it by score):
+---+-------+------+
|id | level |score |
+---+-------+------+
| 4 | 1 | 30 |
| 3 | 1 | 35 |
| 1 | 1 | 40 |
| 5 | 1 | 45 |
| 7 | 1 | 50 |
| 8 | 1 | 55 |
+---+-------+------+
I will output that to php in a while loop. So each row in the while loop will be the same as in the table above.
Essentially what I want to do is show 5 of these rows in a table (in html), with a certain row (e.g. where id=5) in the middle and have the two rows above and below it (in the correct order). This will be like a score board but only showing the user's score with the two above and two below.
E.g. say the user is id=5, I want to show
+---+-------+------+
|id | level |score |
+---+-------+------+
| 3 | 1 | 35 |
| 1 | 1 | 40 |
| 5 | 1 | 45 |
| 7 | 1 | 50 |
| 8 | 1 | 55 |
I am wondering does anyone know a way of doing this in php?
Basically
//select query output is in while loop
//get a certain row of the loop
//get the two rows above it and two rows below it
One method uses a lot of variables:
select t.*
from (select t.*,
lag(id, 1) over (order by score) as prev_id,
lag(id, 2) over (order by score) as prev_id2,
lead(id, 1) over (order by score) as next_id,
lead(id, 2) over (order by score) as next_id2
from t
) t
where 5 in (prev_id, prev_id2, next_id, next_id2, id)
order by score;
An alternative method is something like this:
(select t.*
from t
where t.score <= (select t2.score from t t2 where t2.id = 5)
order by score desc
limit 3
) union all
(select t.*
from t
where t.score > (select t2.score from t t2 where t2.id = 5)
order by score
limit 2
)
order by score;
This exactly syntax may not work in all databases, but the idea can easily be translated in whatever dialect of SQL. This also assumes that the scores are unique.
I have a table like this
+-------+-------+-------+-------+
| id | cid | grade |g_point|
+-------+-------+-------+-------+
| 1 | 10 | A+ | 1 |
| 2 | 10 | A+ | 1 |
| 3 | 10 | B | 3 |
| 4 | 11 | A | 2 |
| 5 | 11 | A+ | 1 |
| 6 | 12 | B | 3 |
the column g_point is the values associated to each grade. forexample A+ grade considers highest so I assign the value of A+ is one(highest starts from 1 to 10) and so on. These g_point values are constant. Now what I want to do is I want to show the maximum grade against each course and also if somehow there are only two entries of different grades I want to compare it with the g_point and choose whose value is lower because lower integer value means higher grade. the result should be like this and also sorted from top grade to lower.
+-------+-------+
| cid | grade |
+-------+-------+
| 10 | A+ |
| 11 | A+ |
| 12 | B |
I have tried this query
SELECT coursecodeID AS cid, (SELECT grade
FROM feedback
WHERE coursecodeID = cid
GROUP BY grade
ORDER BY COUNT(*) DESC LIMIT 0,1) AS g
FROM feedback
GROUP BY coursecodeID
but in this query I don't know how can I compare it with g_point value and also the courses is not showing in order(from highest grade to lowest).
NOTE: I want to choose the grade having the maximum number of occurrences per course id. For example here in this table course id 10 has 2 A+ grade so we'll consider A+ and if clash happens like one is A+ and the other is B+, then we'll have to compare it with the g_point
This works, but needs the 'g_point' to alse be returned.
SELECT cid,grade,MIN(g_point)
FROM grades
GROUP BY cid
This is more reliable, as it generates the Grade in the sub-query, and then appends it to the main table.
SELECT cid, (
SELECT grade
FROM grades g2
WHERE g2.cid = g1.cid
ORDER BY g_point
LIMIT 1
) AS grade
FROM grades g1
GROUP BY cid
You can use the following query:
SELECT DISTINCT m1.cid, m1.grade
FROM mytable AS m1
INNER JOIN (
SELECT cid, MIN(g_point) AS maxGrade
FROM mytable
GROUP BY cid ) m2
ON m1.cid = m2.cid AND m1.g_point = m2.maxGrade
The derived table contains the minimum g_point per cid. If you join it back to the original table, then you can get the maximum grade per cid.
Demo here
EDIT:
You can alternatively use a correlated sub-query:
SELECT cid, (SELECT grade
FROM mytable AS m2
WHERE m2.cid = m1.cid
ORDER BY g_point LIMIT 1) AS maxGrade
FROM mytable AS m1
GROUP BY cid
Demo here
EDIT2:
It looks like you want to get the grade having the maximum number of occurrences per cid. In case there are more than one grades sharing this maximum number, then fetch the grade with the lowest g_point.
You can do it using variables:
SELECT cid, grade
FROM (
SELECT cid, grade,
#row_number := IF (#cid <> cid,
IF (#cid := cid, 1, 1),
IF (#cid := cid, #row_number+1, #row_number+1)) AS rn
FROM (
SELECT cid, grade,
COUNT(*) AS cnt,
(SELECT g_point
FROM mytable AS m2
WHERE m1.grade = m2.grade
LIMIT 1) AS g_point
FROM mytable AS m1
GROUP BY cid, grade
) t
CROSS JOIN (SELECT #row_number:=-1, #cid:=-1) AS vars
ORDER BY cid, cnt DESC, g_point
) s
WHERE rn = 1
Demo here
Something to think about...
SELECT * FROM ints;
+---+
| i |
+---+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
+---+
SELECT i
, CONCAT(CHAR((i/2)+64),IF(MOD(i,2)=1,'+',''))n
FROM ints
WHERE i > 0;
+---+------+
| i | n |
+---+------+
| 1 | A+ |
| 2 | A |
| 3 | B+ |
| 4 | B |
| 5 | C+ |
| 6 | C |
| 7 | D+ |
| 8 | D |
| 9 | E+ |
+---+------+
I have a follow up question about this question of mine if you don't mind..
count total flow inventory in and out php mysql
Table A
=======================================================
**id** | **code** | **status** | **total** | **date** |
1 | B01 | IN | 500 |2013-01-15|
2 | B01 | OUT | 100 |2013-01-20|
3 | B01 | OUT | 200 |2013-02-01|
4 | B01 | IN | 300 |2013-02-05|
from the table above I would like the output like this..
Table A
===================================================================================
**id** | **code** | **status** | **total** | **date** | **stock** | 1st month stock
1 | B01 | IN | 500 |2013-01-15| 500 | -
2 | B01 | OUT | 100 |2013-01-20| 400 | 500
3 | B01 | IN | 200 |2013-02-01| 600 | 400
4 | B01 | OUT | 300 |2013-02-05| 300 | 600
5 | | | | | | 300
how can I achieve that using mysql ? is there a way?
for the stock column I'm already achieve that using this method
select t.*, #stock := #stock + case when status = 'IN'
then total
else -total
end as stock
from your_table t
cross join (select #stock := 0) s
order by t.id
from my previous question, help me I'm really new to mysql
Based on what you already have, you could add another expression to the SELECT list,
select t.*, #stock AS `1st month stock`, #stock := ...
N.B. the return of the current value of #stock would need to come BEFORE a new value is assigned to #stock in the SELECT list, so the order of the columns is slightly different than shown in the desired output.
Based on your current query, just add a new expression to your SELECT list, to return another column, e.g.
select t.*
, #stock AS `1st month stock`
, #stock := #stock + case when status = 'IN'
then total
else -total
end as stock
from your_table t
cross join (select #stock := 0) s
order by t.id
Hi there coders around the world,
I'm working on a project where users can do certain things and gain points for it. To simplify this question let's say we got 2 tables user and points.
-- table user -- table points
+---------------+ +-----------------------------+
| id | name | | id | points | user_id |
+---------------+ +-----------------------------+
| 1 Tim | | 1 5 1 |
| 2 Tom | | 2 10 1 |
| 3 Marc | | 3 5 1 |
| 4 Tina | | 4 12 2 |
| 5 Lutz | | 5 2 2 |
+---------------+ | 6 7 1 |
| 7 40 3 |
| 8 100 1 |
+-----------------------------+
Now to get the complete highscore-list I use the following query
SELECT u.*, SUM( p.points ) AS sum_points
FROM user u
LEFT JOIN points p ON p.user_id = u.id
GROUP BY u.id
ORDER BY sum_points DESC
resulting in a fine highscore-list with all users from first to last
+------------------------------+
| id | name | sum_points |
+------------------------------+
| 1 Tim 127 |
| 3 Marc 40 |
| 2 Tom 14 |
| 4 Tina 0 |
| 5 Lutz 0 |
+------------------------------+
Alright back to the question itself. On the profile of a single user I'd like to show his ranking within the highscore-list.
Can this be done using a single query just showing that for example Tom (id=2) is ranked in place 3?
Thanks alot :-)
The idea is to ask, "how many players rank above #this_user":
select count(*) + 1 from
(
/* list of all users */
SELECT SUM( p.points ) AS sum_points
FROM user u
LEFT JOIN points p ON p.user_id = u.id
GROUP BY u.id
) x
/* just count the ones with higher sum_points */
where sum_points > (select sum(points) from points where user_id = #this_user)
Edited to make result 1-based instead of 0-based
SELECT q.*,
#r := #r + 1 AS rank
FROM (
SELECT #r := 0
) vars,
(
SELECT u.*,
SUM(p.points) AS sum_points
FROM
user u
LEFT JOIN
points p
ON p.user_id = u.id
GROUP BY
u.id
ORDER BY
sum_points DESC
) q