Ranking Students by Grade in SQL - sql

I have a table like this:
Date StudentName Score
01.01.09 Alex 100
01.01.09 Tom 90
01.01.09 Sam 70
01.02.09 Alex 100
01.02.09 Tom 50
01.02.09 Sam 100
I need to rank the students in the result table by score within different dates, like this:
Date Student Rank
01.01.09 Alex 1
01.01.09 Tom 2
01.01.09 Sam 3
01.02.09 Alex 1
01.02.09 Sam 1
01.02.09 Tom 2
How can I do this in SQL?

You want to use the rank function in T-SQL:
select
date,
student,
rank() over (partition by date order by score desc) as rank
from
grades
order by
date, rank, student
The magic is in the over clause. See, it splits up those rankings by date, and then orders those subsets by score. Brilliant, eh?

You should use ORDER BY:
SELECT * FROM Students ORDER BY Date,Rank
That will order the data by date, then rank. You can add as many fields as you want, as long as they are comparable (you can't compare BLOBs or long text fields).
Hope that helps.

Do you know about using an ORDER BY clause?

You need to write a function that will computer the rank for a given student and date. Then you can "ORDER BY Date, Rank()"

Related

How to select only the most recent

Table A has ID and date and name. Each time the record is changed the first 11 digits of the Id remain the same but the final digit would increase by 1. For example
123456789110 01-01-2020 John smith
119876543210 01-01-2020 Peter Griffin
119876543211 05-01-2020 Peter Griffin
How could I write a statement that shows The iD associated with John smith as well as the most recent Id of Peter Griffin? Thanks
Yet another option is using WITH TIES
Select top 1 with ties *
From YourTable
Order by row_number() over (partition by left(id,11) order by date desc)
Why not just use max()?
select name, max(id)
from t
group by name;

How to select students who got above average?

How to list all students who got above average grade of their group in SQL table? We have 6 group_ids so there six different average grades.
group_id student grade
1 James 85
1 Adam 96
2 Tom 56
2 Jane 89
2 Anny 90
Result:
group_id student grade
1 Adam 96
2 Jane 89
2 Anny 90
ashkufaraz's answer is closer but not quite right
select group_id,student,grade from students one where grade >
(select avg(grade) from students two where two.group_id = one.group_id)
The question is just tagged SQL, so this is an answer using standard SQL:
One option is to use a window function:
select group_id,student,grade
from (
select group_id,student,grade,
avg(grade) over (partition by group_id) as group_avg
from studends
) t
where grade > group_avg;
This has the additional benefit that you can also display the group average along with the result with no additional join or sub-select.

Retrieve highest value from sql table

How can retrieve that data:
Name Title Profit
Peter CEO 2
Robert A.D 3
Michael Vice 5
Peter CEO 4
Robert Admin 5
Robert CEO 13
Adrin Promotion 8
Michael Vice 21
Peter CEO 3
Robert Admin 15
to get this:
Peter........4
Robert.......15
Michael......21
Adrin........8
I want to get the highest profit value from each name.
If there are multiple equal names always take the highest value.
select name,max(profit) from table group by name
Since this type of request almost always follows with "now can I include the title?" - here is a query that gets the highest profit for each name but can include all the other columns without grouping or applying arbitrary aggregates to those other columns:
;WITH x AS
(
SELECT Name, Title, Profit, rn = ROW_NUMBER()
OVER (PARTITION BY Name ORDER BY Profit DESC)
FROM dbo.table
)
SELECT Name, Title, Profit
FROM x
WHERE rn = 1;

How to produce detail, not summary, report sorted by count(*)?

Oracle 11g:
I want results to list by highest count, then ch_id. When I use group by to get the count then I loose the granularity of the detail. Is there an analytic function I could use?
SALES
ch_id desc customer
=========================
ANAR Anari BOB
SWIS Swiss JOE
SWIS Swiss AMY
BRUN Brunost SAM
BRUN Brunost ANN
BRUN Brunost ROB
Desired Results
count ch_id customer
===========================================
3 BRUN ANN
3 BRUN ROB
3 BRUN SAM
2 SWIS AMY
2 SWIS JOE
1 ANAR BOB
Use the analytic count(*):
select * from
(
select count(*) over (partition by ch_id) cnt,
ch_id, customer
from sales
)
order by cnt desc
select total, ch_id, customer
from sales s
inner join (select count(*) total, ch_id from sales group by ch_id) b
on b.ch_id = s.chi_id
order by total, ch_id
ok - the other post that happened at the same time, using partition, is the better solution for Oracle. But this one works regardless of DB.

selecting top N rows for each group in a table

I am facing a very common issue regarding "Selecting top N rows for each group in a table".
Consider a table with id, name, hair_colour, score columns.
I want a resultset such that, for each hair colour, get me top 3 scorer names.
To solve this i got exactly what i need on Rick Osborne's blogpost "sql-getting-top-n-rows-for-a-grouped-query"
That solution doesn't work as expected when my scores are equal.
In above example the result as follow.
id name hair score ranknum
---------------------------------
12 Kit Blonde 10 1
9 Becca Blonde 9 2
8 Katie Blonde 8 3
3 Sarah Brunette 10 1
4 Deborah Brunette 9 2 - ------- - - > if
1 Kim Brunette 8 3
Consider the row 4 Deborah Brunette 9 2. If this also has same score (10) same as Sarah, then ranknum will be 2,2,3 for "Brunette" type of hair.
What's the solution to this?
If you're using SQL Server 2005 or newer, you can use the ranking functions and a CTE to achieve this:
;WITH HairColors AS
(SELECT id, name, hair, score,
ROW_NUMBER() OVER(PARTITION BY hair ORDER BY score DESC) as 'RowNum'
)
SELECT id, name, hair, score
FROM HairColors
WHERE RowNum <= 3
This CTE will "partition" your data by the value of the hair column, and each partition is then order by score (descending) and gets a row number; the highest score for each partition is 1, then 2 etc.
So if you want to the TOP 3 of each group, select only those rows from the CTE that have a RowNum of 3 or less (1, 2, 3) --> there you go!
The way the algorithm comes up with the rank, is to count the number of rows in the cross-product with a score equal to or greater than the girl in question, in order to generate rank. Hence in the problem case you're talking about, Sarah's grid would look like
a.name | a.score | b.name | b.score
-------+---------+---------+--------
Sarah | 9 | Sarah | 9
Sarah | 9 | Deborah | 9
and similarly for Deborah, which is why both girls get a rank of 2 here.
The problem is that when there's a tie, all girls take the lowest value in the tied range due to this count, when you'd want them to take the highest value instead. I think a simple change can fix this:
Instead of a greater-than-or-equal comparison, use a strict greater-than comparison to count the number of girls who are strictly better. Then, add one to that and you have your rank (which will deal with ties as appropriate). So the inner select would be:
SELECT a.id, COUNT(*) + 1 AS ranknum
FROM girl AS a
INNER JOIN girl AS b ON (a.hair = b.hair) AND (a.score < b.score)
GROUP BY a.id
HAVING COUNT(*) <= 3
Can anyone see any problems with this approach that have escaped my notice?
Use this compound select which handles OP problem properly
SELECT g.* FROM girls as g
WHERE g.score > IFNULL( (SELECT g2.score FROM girls as g2
WHERE g.hair=g2.hair ORDER BY g2.score DESC LIMIT 3,1), 0)
Note that you need to use IFNULL here to handle case when table girls has less rows for some type of hair then we want to see in sql answer (in OP case it is 3 items).