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.
Related
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);
My task is to find all those subjects, by their id, that have (at least one, but) the fewest lowest passing grades in the database (the grade being the grade 6). I've managed to write the solution with three queries, however my task is to write it as a single query in MySQL. Thank you in advance.
-- 1. single query "solution"
SELECT subject_id FROM (SELECT subject_id, COUNT(*) AS six_count
FROM exams WHERE grade = 6
GROUP BY subject_id) AS sixes
WHERE subject_id = (SELECT MIN(six_count) FROM sixes);
-- 2. multiple queries solution
CREATE TABLE sixes AS (SELECT subject_id, COUNT(*) AS six_count
FROM exams WHERE grade = 6
GROUP BY subject_id);
SELECT subject_id FROM sixes
WHERE subject_id = (SELECT MIN(six_count) FROM sixes);
DROP TABLE sixes;
EDIT:
Exams table example:
| subject_id | student_id | exam_year | exam_mark | grade | exam_date |
| 1 | 20100022| 2011 | 'apr' | 10 | 2011-04-11 |
| 2 | 20100055| 2011 | 'oct' | 6 | 2011-10-04 |
| 3 | 20110030| 2011 | 'jan1' | 7 | 2011-01-26 |
| 5 | 20110055| 2011 | 'jan2' | 6 | 2011-02-13 |
| 5 | 20110001| 2011 | 'jun1' | 8 | 2011-06-23 |
This should do the trick. The sub query selects the first lowest number of sixes. The main query selects all subjects with that number. The trick is in ORDER BY count(*) LIMIT 1, which makes the sub query return the record with the lowest count.
SELECT
subject_id,
count(*) as six_count
FROM exams
WHERE grade = 6
GROUP BY subject_id
HAVING count(*) =
( SELECT count(*)
FROM exams
WHERE grade = 6
GROUP BY subject_id
ORDER BY count(*)
LIMIT 1
)
This pattern should do the trick. Generalized names.
SELECT subjectID
FROM TEST_DATA
WHERE grade = 6
GROUP
BY SubjectID
HAVING COUNT(1) =
( SELECT count(1) AS minCount
FROM TEST_DATA
WHERE grade = 6
GROUP
BY subjectID
ORDER
BY minCount
LIMIT 1
);
I am in a very complicated problem. Let me explain you first what I am doing right now:
I have a table name feedback in which I am storing grades against course id. The table looks like this:
+-------+-------+-------+-------+-----------+--------------
| id | cid | grade |g_point| workload | easiness
+-------+-------+-------+-------+-----------+--------------
| 1 | 10 | A+ | 1 | 5 | 4
| 2 | 10 | A+ | 1 | 2 | 4
| 3 | 10 | B | 3 | 3 | 3
| 4 | 11 | B+ | 2 | 2 | 3
| 5 | 11 | A+ | 1 | 5 | 4
| 6 | 12 | B | 3 | 3 | 3
| 7 | 11 | B+ | 2 | 7 | 8
| 8 | 11 | A+ | 1 | 1 | 2
g_point has just specific values for the grades, thus I can use these values to show the user courses sorted by grades.
Okay, now first my task is to print out the grade of each course. The grade can be calculated by the maximum occurrence against each course. For example from this table we can see the result of cid = 10 will be A+, because it is present two times there. This is simple. I have already implemented this query which I will write here in the end.
The main problem is when we talk about the course cid = 11 which has two different grades. Now in that situation client asks me to take the average of workload and easiness of both these courses and whichever course has the greater average should be shown. The average would be computed like this:
all workload values of the grade against course
+ all easiness values of the grade against course
/ 2
From this example cid = 11 has four entries,have equal number of grades against a course
B+ grade average
avgworkload(2 + 7)/2=x
avgeasiness(3 + 8)/2 = y
answer x+y/2 = 10
A+ grade average
avgworkload(5 + 1)/2=x
avgeasiness(4 + 2)/2 = y
answer x+y/2 = 3
so the grade should be B+.
This is the query which I am running to get the max occurrence grade
SELECT
f3.coursecodeID cid,
f3.grade_point p,
f3.grade g
FROM (
SELECT
coursecodeID,
MAX(mode_qty) mode_qty
FROM (
SELECT
coursecodeID,
COUNT(grade_point) mode_qty
FROM feedback
GROUP BY
coursecodeID, grade_point
) f1
GROUP BY coursecodeID
) f2
INNER JOIN (
SELECT
coursecodeID,
grade_point,
grade,
COUNT(grade_point) mode_qty
FROM feedback
GROUP BY
coursecodeID, grade_point
) f3
ON
f2.coursecodeID = f3.coursecodeID AND
f2.mode_qty = f3.mode_qty
GROUP BY f3.coursecodeID
ORDER BY f3.grade_point
Here is SQL Fiddle.
I added a table Courses with the list of all course IDs, to make the main idea of the query easier to see. Most likely you have it in the real database. If not, you can generate it on the fly from feedback by grouping by cid.
For each cid we need to find the grade. Group feedback by cid, grade to get a list of all grades for the cid. We need to pick only one grade for a cid, so we use LIMIT 1. To determine which grade to pick we order them. First, by occurrence - simple COUNT. Second, by the average score. Finally, if there are several grades than have same occurrence and same average score, then pick the grade with the smallest g_point. You can adjust the rules by tweaking the ORDER BY clause.
SELECT
courses.cid
,(
SELECT feedback.grade
FROM feedback
WHERE feedback.cid = courses.cid
GROUP BY
cid
,grade
ORDER BY
COUNT(*) DESC
,(AVG(workload) + AVG(easiness))/2 DESC
,g_point
LIMIT 1
) AS CourseGrade
FROM courses
ORDER BY courses.cid
result set
cid CourseGrade
10 A+
11 B+
12 B
UPDATE
MySQL doesn't have lateral joins, so one possible way to get the second column g_point is to repeat the correlated sub-query. SQL Fiddle
SELECT
courses.cid
,(
SELECT feedback.grade
FROM feedback
WHERE feedback.cid = courses.cid
GROUP BY
cid
,grade
ORDER BY
COUNT(*) DESC
,(AVG(workload) + AVG(easiness))/2 DESC
,g_point
LIMIT 1
) AS CourseGrade
,(
SELECT feedback.g_point
FROM feedback
WHERE feedback.cid = courses.cid
GROUP BY
cid
,grade
ORDER BY
COUNT(*) DESC
,(AVG(workload) + AVG(easiness))/2 DESC
,g_point
LIMIT 1
) AS CourseGPoint
FROM courses
ORDER BY CourseGPoint
result set
cid CourseGrade CourseGPoint
10 A+ 1
11 B+ 2
12 B 3
Update 2 Added average score into ORDER BY SQL Fiddle
SELECT
courses.cid
,(
SELECT feedback.grade
FROM feedback
WHERE feedback.cid = courses.cid
GROUP BY
cid
,grade
ORDER BY
COUNT(*) DESC
,(AVG(workload) + AVG(easiness))/2 DESC
,g_point
LIMIT 1
) AS CourseGrade
,(
SELECT feedback.g_point
FROM feedback
WHERE feedback.cid = courses.cid
GROUP BY
cid
,grade
ORDER BY
COUNT(*) DESC
,(AVG(workload) + AVG(easiness))/2 DESC
,g_point
LIMIT 1
) AS CourseGPoint
,(
SELECT (AVG(workload) + AVG(easiness))/2
FROM feedback
WHERE feedback.cid = courses.cid
GROUP BY
cid
,grade
ORDER BY
COUNT(*) DESC
,(AVG(workload) + AVG(easiness))/2 DESC
,g_point
LIMIT 1
) AS AvgScore
FROM courses
ORDER BY CourseGPoint, AvgScore DESC
result
cid CourseGrade CourseGPoint AvgScore
10 A+ 1 3.75
11 B+ 2 5
12 B 3 3
If I understood well you need an inner select to find the average, and a second outer select to find the maximum values of the average
select cid, grade, max(average)/2 from (
select cid, grade, avg(workload + easiness) as average
from feedback
group by cid, grade
) x group by cid, grade
This solution has been tested on your data usign sql fiddle at this link
If you change the previous query to
select cid, max(average)/2 from (
select cid, grade, avg(workload + easiness) as average
from feedback
group by cid, grade
) x group by cid
You will find the max average for each cid.
As mentioned in the comments you have to choose wich strategy use if you have more grades that meets the max average. For example if you have
+-------+-------+-------+-------+-----------+--------------
| id | cid | grade |g_point| workload | easiness
+-------+-------+-------+-------+-----------+--------------
| 1 | 10 | A+ | 1 | 5 | 4
| 2 | 10 | A+ | 1 | 2 | 4
| 3 | 10 | B | 3 | 3 | 3
| 4 | 11 | B+ | 2 | 2 | 3
| 5 | 11 | A+ | 1 | 5 | 4
| 9 | 11 | C | 1 | 3 | 6
You will have grades A+ and C soddisfing the maximum average 4.5
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
By any chance you would be able to help optimize this query without me showing you the tables?
My original table that all of these queries are derived from has the following columns and the table is named laterec-students
--------------------------------------------------------------
| studentid | name | class | latetime | waived |
--------------------------------------------------------------
| ID1111STU | Stu 1 | 1A |2012-01-09 08:09:00 |Waived |
SELECT A.class, NoStudentsLate, 1xLATE, 2xLATE FROM (
SELECT
class,
count(DISTINCT studentid) AS NoStudentsLate
FROM `laterec-students`
WHERE waived!="Waived"
GROUP BY class
) AS A
LEFT JOIN (
SELECT class, count(distinct studentid) AS 1xLATE from (
SELECT `laterec-students`.class, `laterec-students`.studentid
FROM `laterec-students`
WHERE waived!="Waived"
GROUP BY studentid
HAVING count(studentid)=1) as temp
GROUP BY class
) AS B ON A.class=B.class
LEFT JOIN (
SELECT class, count(distinct studentid) AS 2xLATE from (
SELECT `laterec-students`.class, `laterec-students`.studentid
FROM `laterec-students`
WHERE waived!="Waived"
GROUP BY studentid
HAVING count(studentid)=2) as temp
GROUP BY class
) AS C ON A.class=C.class
This is what I am trying to accomplish
---------------------------------------------------------------------
| Class | Total # of students late | # late 1 times | # late 2 times |
---------------------------------------------------------------------
| 1A | 5 | 3 | 2 |
| 1B | 3 | 3 | 0 |
---------------------------------------------------------------------
So what this means, in class 1A, there are a total of 5 student late as counted using the student id. Out of this 5, 3 students are late once, and 2 students are late twice.
Again in class 1B, total 3 students are late, and all of them are only late once.
I hope that I understood your query, but the following works with my SQL Fiddle example.
SELECT
class,
SUM(cnt > 0) AS NoStudentsLate,
SUM(cnt = 1) AS 1xLate,
SUM(cnt = 2) AS 2xLate
FROM
(
SELECT class, studentid, COUNT(*) AS cnt
FROM `laterec-students`
WHERE waived!='Waived'
GROUP BY class, studentid
) t
GROUP BY class;