How to get the nth Highest mark in all branch? - mysql

I have a table Student with fields: Student_id, Student_Name, Mark, Branch.
I want to get the nth highest mark and name of each branch with in a single query. Is it possible?
for Example if the datas are
S1 | Amir | EC | 121
S2 | Ewe | EC | 123
S3 | Haye | EC | 45
S4 | Mark | EC | 145
S5 | Tom | CS | 152
S6 | Hudd | CS | 218
S7 | Ken | CS | 48
S8 | Ben | CS | 15
S9 | Wode | CS | 123
S10 | Kayle | IT | 125
S11 | Den | IT | 120
S12 | Noy | IT | 126
And I am selecting to display the third highest mark in each branch the output should be like
S1 | Amir | EC | 121
S9 | Wode | CS | 123
S11 | Den | IT | 120

This would be much easier if MySQL had windowing functions like several of the other answers have shown. But they don't so you can use something like the following:
select student_id,
student_name,
branch,
mark
from
(
select student_id,
student_name,
branch,
mark,
#num := if(#branch = `branch`, #num + 1, 1) as group_row_number,
#branch := `branch` as dummy,
overall_row_num
from
(
select student_id,
student_name,
branch,
mark,
#rn:=#rn+1 overall_row_num
from student, (SELECT #rn:=0) r
order by convert(replace(student_id, 'S', ''), signed)
) src
order by branch, mark desc
) grp
where group_row_number = 3
order by overall_row_num
See SQL Fiddle with Demo
The result would be:
| STUDENT_ID | STUDENT_NAME | BRANCH | MARK |
---------------------------------------------
| S1 | Amir | EC | 121 |
| S9 | Wode | CS | 123 |
| S11 | Den | IT | 120 |

MAX()
select branch, MAX(mark) as 'highest mark' from Student group by branch;

MySQl does not support CTE and also rownumber, so you have do something like this :-
Make a temp table , insert data into that then select and finally drop temp table ..
create table #temp(branch varchar(30),mark int,row_numbers int)
Insert into #temp
SELECT a.branch, a.mark, count(*) as row_numbers FROM student a
JOIN student b ON a.branch = b.branch AND a.mark <= b.mark
GROUP BY a.mark,a.branch
Order by a.mark desc
select branch,mark from #temp where row_numbers=2
drop table #temp
This is for SQL SERVER :-
;WITH CTE AS
(
SELECT Branch,mark, ROW_NUMBER() OVER (PARTITION BY Branch ORDER BY mark DESC) AS RowNum
FROM
student
)
SELECT Branch,mark from cte
WHERE RowNum = 2
This will give suppose 2nd highest mark branch wise, you can accordingly choose any Nth level .
Hope it helps :-

This one works in oracle
select Student_id,Student_Name,Mark,Branch from (
select Student_id,Student_Name,Mark,Branch,dense_rank() over (partition by Branch order by Mark desc) rnk
from Student ) where rnk=nth_value;
nth_value:=nth highest mark needed.
please check this sqlfiddle: http://sqlfiddle.com/#!4/7b559/3

You can use Limit to get n highest market ( limit 1,1 will give you 2nd highest, set as per your requirement)
SELECT mark, name
FROM student ORDER BY mark
DESC LIMIT 1,1

Related

How do I update scores in table without using a ranking function

Table name is: result
ID Name score position
1 John 40 0
2. Ali 79 0
3 Ben 50 0
4 Joe 79 0
How can I update table result to give me the table below without using rank() as it does not support by server. Pls someone should help me with the MySQL code That breaks ties just as in table below.
ID Name score position
1 John 40 4
2. Ali 79 1
3 Ben 50 3
4 Joe 79 1
In MySQL prior to version 8 try using the multiple table update syntax:
UPDATE scores t
LEFT JOIN (
SELECT t1.id, COUNT(*) + 1 AS new_position
FROM scores t1
JOIN scores t2 ON t1.score < t2.score
GROUP BY t1.id
) agg ON t.id = agg.id
SET t.position = COALESCE(agg.new_position, 1)
fiddle
Lots of ways to skin this particular animal. How about...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(ID SERIAL PRIMARY KEY
,Name VARCHAR(12) NOT NULL
,score INT NOT NULL
);
INSERT INTO my_table VALUES
(1,'John',40),
(2,'Ali',79),
(3,'Ben',50),
(4,'Joe',79);
SELECT id
, name
, score
, FIND_IN_SET(score, scores) rank
FROM my_table
CROSS
JOIN
( SELECT GROUP_CONCAT(score ORDER BY score DESC) scores
FROM my_table
) scores
+----+------+-------+------+
| id | name | score | rank |
+----+------+-------+------+
| 1 | John | 40 | 4 |
| 2 | Ali | 79 | 1 |
| 3 | Ben | 50 | 3 |
| 4 | Joe | 79 | 1 |
+----+------+-------+------+
I've not provided an UPDATE, because you wouldn't normally store derived data.
You can use correlated sub-query as follows:
update your_table t
set t.position = (select count(*) + 1 from your_table tt
where tt.score > t.score)

Find the highest value of each type after summing like values MySQL

I have a MySQL table that looks something like this:
Name | Type | aID | bID | cnt
s1 | ta | 1 | 10 | 5
s2 | tb | 1 | 11 | 10
s3 | ta | 1 | 12 | 11
s1 | ta | 1 | 23 | 2
s2 | tb | 1 | 54 | 4
s3 | ta | 2 | 23 | 7
s4 | tc | 1 | 93 | 5
s4 | tc | 2 | 82 | 2
Given an aID, I want to return the top element of each type after summing values with the same Name and aID together.
For example, given an aID of one, the result should be:
s3 of type ta with cnt of 11.
s2 of type tb with cnt of 14.
s4 of type tc with cnt of 5.
There are a large number of aIDs and ~10 or so types. Is this possible?
You can use analytic functions for a MySQL 8+ option:
WITH cte AS (
SELECT Name, Type, aID, SUM(cnt) AS cnt,
ROW_NUMBER() OVER (PARTITION BY Name, aID
ORDER BY SUM(cnt) DESC) rn
FROM yourTable
GROUP BY Name, Type, aID
)
SELECT Name, Type, aID, cnt
FROM cte
WHERE rn = 1
ORDER BY aID, Name;
Demo
This query works by first aggregating by Name, Type, and aID, generating sums of counts for each group. We then use ROW_NUMBER with a partition only on the Name and aID, to find the row having the Type with the maximum count. Keep in mind that analytic functions are applied after the GROUP BY has happened, so ROW_NUMBER here operates on the aggregate rows, not the rows of your original table.
How about this simple query?:
SELECT
Type, MAX(t.total)
FROM
(SELECT
Name, Type, SUM(cnt) total
FROM
tablename
WHERE
aID = 1
GROUP BY
Name, Type ) t
GROUP BY
Type;

Find the students who attended at least one exam but not the Max nor the Min score

My solution passed the first test case, but got the wrong answer after final submission. I am thankful for anyone willing to point out my mistakes. Thanks!
The question is as below:
Table: Student
+---------------------+---------+
| Column Name | Type |
+---------------------+---------+
| student_id | int |
| student_name | varchar |
+---------------------+---------+
student_id is the primary key for this table.
student_name is the name of the student.
Table: Exam
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| exam_id | int |
| student_id | int |
| score | int |
+---------------+---------+
(exam_id, student_id) is the primary key for this table.
Student with student_id got score points in exam with id exam_id.
A "quite" student is the one who took at least one exam and didn't score neither the high score nor the low score.
Write an SQL query to report the students (student_id, student_name) being "quiet" in ALL exams.
Don't return the student who has never taken any exam. Return the result table ordered by student_id.
The query result format is in the following example.
Student table:
+-------------+---------------+
| student_id | student_name |
+-------------+---------------+
| 1 | Daniel |
| 2 | Jade |
| 3 | Stella |
| 4 | Jonathan |
| 5 | Will |
+-------------+---------------+
Exam table:
+------------+--------------+-----------+
| exam_id | student_id | score |
+------------+--------------+-----------+
| 10 | 1 | 70 |
| 10 | 2 | 80 |
| 10 | 3 | 90 |
| 20 | 1 | 80 |
| 30 | 1 | 70 |
| 30 | 3 | 80 |
| 30 | 4 | 90 |
| 40 | 1 | 60 |
| 40 | 2 | 70 |
| 40 | 4 | 80 |
+------------+--------------+-----------+
Result table:
+-------------+---------------+
| student_id | student_name |
+-------------+---------------+
| 2 | Jade |
+-------------+---------------+
Explanation in the question:
For exam 1: Student 1 and 3 hold the lowest and high score respectively.
For exam 2: Student 1 hold both highest and lowest score.
For exam 3 and 4: Studnet 1 and 4 hold the lowest and high score respectively.
Student 2 and 5 have never got the highest or lowest in any of the exam.
Since student 5 is not taking any exam, he is excluded from the result.
So, we only return the information of Student 2.
My answer was to create two tables, the one is to list eligible student, with at least one exam.
The other is to find the max(score) and the min(score) of the exam table.
And use <> to locate the quiet students' id and then join with Student table to find
this student_id's name, as below:
-- get eligible student
with eligible_student as
(
select distinct student_id as eligible_id from Exam
group by 1
order by 1
),
-- get the high and low score
high_low as
(select student_id, max(score) as high_score, min(score) as low_score
from Exam),
result as
(select eligible_student.eligible_id as student_id
from eligible_student inner join
high_low
on eligible_student.eligible_id <> high_low.student_id
-- left join Student
-- on eligible_student.eligible_id = Student.student_id
group by student_id
order by student_id
)
select result.student_id, s.student_name as student_name
from result left join Student s
on result.student_id = s.student_id
order by student_id;
I would use window functions and aggregation:
select s.*
from student s
inner join (
select e.*,
rank() over(partition by exam_id order by score) as rn_asc,
rank() over(partition by exam_id order by score desc) as rn_desc
from exam e
) e on e.student_id = s.student_id
group by s.student_id
having min(rn_asc) > 1 and min(rn_desc) > 1
The subquery ranks records having he same exam by ascending and descending score, allowing ties. We can then join that with the student table (which eliminates students that have no exam at all), group by student, and filter on those whose both ranks never reached 1.
This query:
SELECT *,
(MIN(score) OVER (PARTITION BY exam_id) = score) +
(MAX(score) OVER (PARTITION BY exam_id) = score) flag
FROM exam
returns a flag column which has the value 0 when the student's score is neither the min nor the max of of the exam.
You can aggregate on the results of the above query to get all the students that have not a single flag with value different than 0:
WITH cte AS (
SELECT *,
(MIN(score) OVER (PARTITION BY exam_id) = score) +
(MAX(score) OVER (PARTITION BY exam_id) = score) flag
FROM exam
)
SELECT s.student_id, s.student_name
FROM student s INNER JOIN cte c
ON c.student_id = s.student_id
GROUP BY s.student_id, s.student_name
HAVING SUM(c.flag) = 0
Or:
WITH cte AS (
SELECT student_id
FROM (
SELECT *,
(MIN(score) OVER (PARTITION BY exam_id) = score) +
(MAX(score) OVER (PARTITION BY exam_id) = score) flag
FROM exam
) t
GROUP BY student_id
HAVING SUM(flag) = 0
)
SELECT *
FROM student
WHERE student_id IN (SELECT student_id FROM cte)
See the demo.
Results:
> student_id | student_name
> ---------: | :-----------
> 2 | Jade
This part is wrong:
with high_low as
(select student_id, max(score) as high_score, min(score) as low_score
from Exam)
because it outputs:
+------------+------------+-----------+
| student_id | high_score | low_score |
+------------+------------+-----------+
| 1 | 90 | 60 |
+------------+------------+-----------+
and student_id=1 has no relation to the found high_score or low_score.
After this the found (but incorrect) student_id is used in the selection for the cte result.
A solution:
with high_low as
(select max(score) as high_score, min(score) as low_score
from Exam)
select student.student_id, student_name
from (
select exam.student_id
from exam
group by exam.student_id
having max(score) <> (select high_score from high_low)
and min(score) <> (select low_score from high_low)) x
inner join student on student.student_id=x.student_id;
or:
select
student.*
from exam
inner join student on student.student_id=exam.student_id
group by student_id
having max(score) not in (select max(score) from exam)
and min(score) not in (select min(score) from exam);

Class wise, student name who has max aggregate of marks

Suppose i have a table as follows:
Class | Subject | Student | Marks
----------------------------------------
1 | Maths | A | 70
1 | Eng | B | 80
1 | IT | A | 90
1 | IT | C | 80
2 | Maths | D | 60
2 | Eng | E | 75
2 | Maths | E | 90
2 | IT | F | 80
3 | Maths | A | 160
3 | Eng | B | 165
3 | IT | G | 90
I want the output as
Class | Student | Marks
----------------------------------------
1 | A | 160
2 | E | 165
3 | B | 165
i.e. Result contains class wise, student name who has max aggregate of marks.
How to write a SQL query for this?
e.g. for class 1, student A has 70+90 = 160, which becomes maximum over B and C both with 80.
One solution is to calculate the maximum points a student has per class, and use that as a filtering join:
select ClassStudentSum.*
from (
select class
, student
, sum(Marks) as SumMarks
from YourTable
group by
class
, student
) as ClassStudentSum
join (
select class
, max(SumMarks) as MaxSumMarks
from (
select class
, student
, sum(Marks) as SumMarks
from YourTable
group by
class
, student
) ClassStudentSum2
group by
class
) MaxPerClass
on MaxPerClass.class = ClassStudentSum.class
and MaxPerClass.MaxSumMarks = ClassStudentSum.SumMarks
Live example at SQL Fiddle.
A more traditional (and so slower) approach...
SELECT x.*
FROM
( SELECT class
, student
, SUM(marks) ttl_marks
FROM yourtable
GROUP
BY class
, student
) x
LEFT
JOIN
( SELECT class
, student
, SUM(marks) ttl_marks
FROM yourtable
GROUP
BY class
, student
) y
ON y.class = x.class
AND y.ttl_marks > x.ttl_marks
WHERE y.class IS NULL;
Try this query
Query 1:
select a.*, if(#prv=class, 1, 0) as flag, #prv:=class from
(select class,student, sum(marks) as total from table1
group by class, student
order by class, total desc)a join (select #prv:=0)tmp
where if(#prv=class, 1, 0) = 0
SQL FIDDLE:
| CLASS | STUDENT | TOTAL | FLAG | #PRV:=CLASS |
------------------------------------------------
| 1 | A | 160 | 0 | 1 |
| 2 | E | 165 | 0 | 2 |
| 3 | B | 165 | 0 | 3 |
Hope this helps
The correct query is:
select class, student, sums.mark
from (select class, student, sum(marks) as mark
from student
group by class, student
order by mark desc) sums
group by class
Use this Statement.it is works
select Class,Student,Marks
from(
select Class,Student,Marks,Dense_rank() over( partition by class order by marks desc) Rank
from(Select Class, Student,Max(Marks) Marks
from(select Class, Student,Sum(Marks) Marks from Temp14
group by Class,Student
order by 1)
group by Class, Student
order by 1)
)
where Rank=1
Try this one.
Here is a simpler query, I guess. It works fine. No need of joins.
SELECT class,
(
SELECT student FROM yourtable
WHERE class = YT.class GROUP BY student
ORDER BY SUM(marks) DESC
LIMIT 1
) AS student,
(
SELECT SUM(marks) FROM yourtable
WHERE class = YT.class GROUP BY student
ORDER BY SUM(marks) DESC
LIMIT 1
) AS marks
FROM yourtable AS YT
GROUP BY class
SQL> with cte as
2 (select class, student, sum(marks) marks
3 from engineer
4 group by class, student
5 order by class)
6 select class, student, marks
7 from (select class, student, marks, dense_rank() over(partition by class order by marks desc) rank
8 from cte)
9 where rank=1;
CLASS S MARKS
---------- - ----------
1 A 160
2 E 165
3 B 165
SQL>
Here the most important inner query is for cte table. Resulr set created for this one will be as below.
SQL> select class, student, sum(marks)
2 from engineer
3 group by class, student
4 order by class;
CLASS S SUM(MARKS)
---------- - ----------
1 A 160
1 B 80
1 C 80
2 D 60
2 E 165
2 F 80
3 A 160
3 B 165
3 G 90
9 rows selected.

Use 'JOIN' to select top five records from a table that match a specific column in table two

I have two table:
Audit
AUDITID | CUSTOMER | CUSTOMERNUMBER
001 | BILLY | 11111
002 | HOLLY | 12222
003 | HOLLY | 12222
004 | DON | 13333
005 | DON | 13333
Summary
AuditID | Summary | Date
001 | 1 | 30/1/2012
001 | 2 | 1/10/2012
001 | 3 |20/10/2012
004 | 4 | 2/09/2012
004 | 5 | 3/01/2012
I want to select the top five records table for each different AuditID that matches an Audit ID From the Audit table.
The sql script i have so far is :
SELECT Auditid, summary, date
FROM [Summary] SL1
INNER JOIN [Audit] AL1 ON SL1.[AuditID] = AL1.[AuditID]
WHERE AL1.[AuditID] IN (
SELECT TOP 5 AuditID
FROM [Audit] AL2
WHERE AL1.[CustomerNumber] = AL2.[CustomerNumber]
ORDER BY AL2.[AuditID] DESC
)
You can use ROW_NUMBER with PARTITION BY:
WITH CTE AS
(
SELECT Auditid, summary, date,
, RN = ROW_NUMBER() OVER (PARTITION BY SL1.AuditID ORDER BY SL1.Date ASC)
FROM [Summary] SL1
INNER JOIN [Audit] AL1 ON SL1.[AuditID] = AL1.[AuditID]
)
SELECT Auditid, summary, date FROM CTE
WHERE RN <= 5
That returns the TOP 5 records for each AuditID ordered by Date(oldest first, use DESC otherwise).
You have to specify which order you want the "top" records from summary - I chose to do it by summary, you can do by date or something. Also I did top2 to show results using your sample data.
select audit.auditid,Summary,DATE
from
Audit inner join Summary Sum_Tab1 on Audit.AUDITID=Sum_Tab1.AUDITID
where
Sum_Tab1.SUMMARY in
(SELECT top 2 SUMMARY from Summary Sum_Tab2
where Sum_Tab1.AUDITID=Sum_Tab2.AUDITID order by summary)