So I need to select all the students, having the minimum grade for each prof. For example, if Augustinus had two students with grade 1.0, then I would like to see both in the result.
Table of my data
What the result could look like, if the LIMIT was set to 10
So what I basically want is to see the best students that each prof has.
What I have tried is the following:
SELECT professor, student, min(note)
FROM temp
GROUP BY professor
ORDER BY note
The problem of course being that I only get one minimum value for each prof and not all minimum values.
*temp is just the table name
One way to solve these types of problems is to use a subquery to rank the grades for each class in a descending order. This involves a window function. With a second query you can limit the results based on your criteria of 10.
SELECT professor, student, note
FROM
(
SELECT professor,student,note,
row_number() over(partition by professor order by note desc) as downwardrank
) as rankings
WHERE
downwardrank <= 10
Just found a solution myself:
SELECT professor, student, note
FROM temp
WHERE (professor, note) IN
(SELECT professor, min(note)
FROM temp
GROUP BY professor
ORDER BY note)
ORDER BY note, professor, student
LIMIT 10
Related
I need help in writing an efficient query to find a list of toppers (students with maximum total marks in each class) when we are given individual scores for each subject across different classes. We are required to return 3 columns: class, topper_student name and topper_student_total marks.
I have used multiple sub-queries to find a solution. I am sure there would be much better implementations available for this problem (maybe via joins or window functions?).
Input table and my solution can be found at SQL Fiddle link.
http://www.sqlfiddle.com/#!15/2919e/1/0
Input table:
It would be clearer to use temporary tables to store results along the way and make the result traceable, but the solution can be achieved with a single query:
WITH student_marks AS (
SELECT Class_num, Name, SUM(Marks) AS student_total_marks
FROM School
GROUP BY Class_num, Name
)
SELECT Class_num, Name, student_total_marks
FROM (
SELECT Class_num, Name, student_total_marks, ROW_NUMBER() OVER(partition by Class_num order by student_total_marks desc, Class_num) AS beststudentfirst
FROM student_marks
) A
WHERE A.beststudentfirst = 1
The query within WITH statement calculate a sum of marks for every student in a class. At this point, subject is not required anymore. The result is temporarily stored into student_marks.
Next, we need to create a counter (beststudentfirst) using ROW_NUMBER to number the total marks from the highest to the lowest in each class (order by student_total_marks desc, Class_num). The counter should be reinitiated each time the class changes (partition by Class_num order).
From this last result, we only need the counter (beststudentfirst) with the value of one. It is the top student in each class.
Window functions are the most natural way to approach this. If you always want exactly three students, then use row_number():
select Class_num, Name, total_marks
from (select name, class_num, sum(marks) as total_marks,
row_number() over (partition by class_num order by sum(marks) desc) as seqnum
from School
group by Class_num, Name
) s
where seqnum <= 1
order by class_num, total_marks desc;
If you want to take ties into account, then use rank() or dense_rank().
Here is the SQL Fiddle.
select Class_num,[Name],total_marks from
(
select Row_number() over (partition by class_num order by Class_num,SUM(Marks) desc) as
[RN],Class_num,[Name],SUM(Marks) as total_marks
from School
group by Class_num,[Name]
)A
where RN=1
I need to count students in every major for an academic year. There are three terms in a year. One student declares a different major in every terms . I need to take the last major he/she declared and count all the students in a major. So only one student for one major.
When I do group by by major, I can't avoid the duplicates.
I have only one table. It has everything I need.
I wrote this code. And It gives me the duplicated count.
SELECT MAJR_CODE, MAJR_DESC, COUNT(DISTINCT ID_KEY)
FROM STUDENT_ENROLLMENT
WHERE TERM in ('201830','201910','201920')
and REGISTERED='Y'
GROUP BY MAJR_CODE, MAJR_DESC
ORDER BY MAJR_CODE
How can I get the result I want?
You can use window functions to get the data for the most recent term:
SELECT MAJR_CODE, MAJR_DESC, COUNT(*)
FROM (SELECT se.*, ROW_NUMBER() OVER (PARTITION BY ID_KEY DESC ORDER BY TERM DESC) as seqnum
FROM STUDENT_ENROLLMENT se
WHERE TERM in ('201830', '201910', '201920') AND
REGISTERED = 'Y'
) se
WHERE seqnum = 1
GROUP BY MAJR_CODE, MAJR_DESC
ORDER BY MAJR_CODE
I have a table named grades. A column named Students, Practical, Written. I am trying to figure out the top 5 students by total score on the test. Here are the queries that I have not sure how to join them correctly. I am using oracle 11g.
This get's me the total sums from each student:
SELECT Student, Practical, Written, (Practical+Written) AS SumColumn
FROM Grades;
This gets the top 5 students:
SELECT Student
FROM ( SELECT Student,
, DENSE_RANK() OVER (ORDER BY Score DESC) as Score_dr
FROM Grades )
WHERE Student_dr <= 5
order by Student_dr;
The approach I prefer is data-centric, rather than row-position centric:
SELECT g.Student, g.Practical, g.Written, (g.Practical+g.Written) AS SumColumn
FROM Grades g
LEFT JOIN Grades g2 on g2.Practical+g2.Written > g.Practical+g.Written
GROUP BY g.Student, g.Practical, g.Written, (g.Practical+g.Written) AS SumColumn
HAVING COUNT(*) < 5
ORDER BY g.Practical+g.Written DESC
This works by joining with all students that have greater scores, then using a HAVING clause to filter out those that have less than 5 with a greater score - giving you the top 5.
The left join is needed to return the top scorer(s), which have no other students with greater scores to join to.
Ties are all returned, leading to more than 5 rows in the case of a tie for 5th.
By not using row position logic, which varies from darabase to database, this query is also completely portable.
Note that the ORDER BY is optional.
With Oracle's PLSQL you can do:
SELECT score.Student, Practical, Written, (Practical+Written) as SumColumn
FROM ( SELECT Student, DENSE_RANK() OVER (ORDER BY Score DESC) as Score_dr
FROM VOTES ) as score, students
WHERE score.score_dr <= 5
and score.Student = students.Student
order by score.Score_dr;
You can easily include the projection of the first query in the sub-query of the second.
SELECT Student
, Practical
, Written
, tot_score
FROM (
SELECT Student
, Practical
, Written
, (Practical+Written) AS tot_score
, DENSE_RANK() OVER (ORDER BY (Practical+Written) DESC) as Score_dr
FROM Grades
)
WHERE Student_dr <= 5
order by Student_dr;
One virtue of analytic functions is that we can just use them in any query. This distinguishes them from aggregate functions, where we need to include all non-aggregate columns in the GROUP BY clause (at least with Oracle).
I have a table Student in SQL Server with these columns:
[ID], [Age], [Level]
I want the query that returns each age value that appears in Students, and finds the level value that appears most often. For example, if there are more 'a' level students aged 18 than 'b' or 'c' it should print the pair (18, a).
I am new to SQL Server and I want a simple answer with nested query.
You can do this using window functions:
select t.*
from (select age, level, count(*) as cnt,
row_number() over (partition by age order by count(*) desc) as seqnum
from student s
group by age, level
) t
where seqnum = 1;
The inner query aggregates the data to count the number of levels for each age. The row_number() enumerates these for each age (the partition by with the largest first). The where clause then chooses the highest values.
In the case of ties, this returns just one of the values. If you want all of them, use rank() instead of row_number().
One more option with ROW_NUMBER ranking function in the ORDER BY clause. WITH TIES used when you want to return two or more rows that tie for last place in the limited results set.
SELECT TOP 1 WITH TIES age, level
FROM dbo.Student
GROUP BY age, level
ORDER BY ROW_NUMBER() OVER(PARTITION BY age ORDER BY COUNT(*) DESC)
Or the second version of the query using amount each pair of age and level, and max values of count pair age and level per age.
SELECT *
FROM (
SELECT age, level, COUNT(*) AS cnt,
MAX(COUNT(*)) OVER(PARTITION BY age) AS mCnt
FROM dbo.Student
GROUP BY age, level
)x
WHERE x.cnt = x.mCnt
Demo on SQLFiddle
Another option but will require later version of sql-server:
;WITH x AS
(
SELECT age,
level,
occurrences = COUNT(*)
FROM Student
GROUP BY age,
level
)
SELECT *
FROM x x
WHERE EXISTS (
SELECT *
FROM x y
WHERE x.occurrences > y.occurrences
)
I realise it doesn't quite answer the question as it only returns the age/level combinations where there are more than one level for the age.
Maybe someone can help to amend it so it includes the single level ages aswell in the result set: http://sqlfiddle.com/#!3/d597b/9
with combinations as (
select age, level, count(*) occurrences
from Student
group by age, level
)
select age, level
from combinations c
where occurrences = (select max(occurrences)
from combinations
where age = c.age)
This finds every age and level combination in the Students table and counts the number of occurrences of each level.
Then, for each age/level combination, find the one whose occurrences are the highest for that age/level combination. Return the age and level for that row.
This has the advantage of not being tied to SQL Server - it's vanilla SQL. However, a window function like Gordon pointed out may perform better on SQL Server.
I have the below example:
SELECT name, age, location, SUM(pay)
FROM employee
GROUP BY location
This as expected will give me an error:
ORA-00979: not a GROUP BY expression
How can I get around this? I need to group by one maybe two columns but need to return all columns even if they're not used in the GROUP BY clause, I've looked at sub-queries to get around it but have had no luck so far.
You can use analytic functions:
SELECT name
, age
, location
, pay
, SUM(pay) over (partition by location order by location ) total
FROM employee
So, you can return all rows even if they are not used in the grouping.
So you want to know the total pay by location, and you want to know the names and ages of employees at each location? How about:
SELECT e.NAME,
e.AGE,
e.LOCATION,
t.TOTAL_LOCATION_PAY
FROM EMPLOYEE e
INNER JOIN (SELECT LOCATION,
SUM(PAY) AS TOTAL_LOCATION_PAY
FROM EMPLOYEE
GROUP BY LOCATION) t
ON (t.LOCATION = e.LOCATION)
Share and enjoy.
(Group b[http://docs.oracle.com/javadb/10.6.2.1/ref/rrefsqlj32654.html] Must have an aggregate function in every column that is not in the group by clause. When you are grouping, means that you want one row per group. Distinct values of the columns in the clause appear in the final result set.
This is because oracle can't know which of the values for the column that you don't have in the group by to retrieve. Consider this:
A X
B X
Select col1, col2 from myTable group by col2; -- incorrect
Select min(col1), col2 from myTable group by col2; -- correct
Why is the first incorrect? Because oracle can't know whether to retrieve A or B for the X value you have to specify it. i.e. MIN, MAX, etc.
There is an alternative to this named analytic functions that allow you to work under windows of your result set.
Now if you want total employee pay by location, and every employee you may want this.
SELECT name, age, location, SUM(pay) OVER(PARTITION BY location)
FROM employee
I believe this is better than #Bob Jarvis query as you only query the table once. Please correct me if I'm wrong. He also has employees and employee. Typo?