Problems selecting columns with aggregates (SQL Server) - sql

I'm seriously stuck. Please bear with me though because I'm new to databases.:)
Anyway, I need to display the StudentID, the subject where the student has the highest grade in, and the grade of that subject.
Here's the code I have:
SELECT
Grades.Student_ID,
Subject.Subject_Code,
MAX(Grades.Grade)
FROM
Grades
LEFT JOIN
Subject ON Grades.Subject_ID = Subject.Subject_ID
GROUP BY
Grades.Student_ID
But it has this error:
'Subject.Subject_Code' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.'
But I can't include Subject_Code in GROUP BY because the results will be different.
What can I do to only show the
Student_ID || (subject with highest grade) || (grade of that subject)
How can I work around this error?

It seems you are looking for a group wise maximum. Here's one approach joining back to a derived table containing the top grade for each student (This approach should work on most RDBMS, including MySql):
SELECT X.Student_ID,
s.Subject_Code,
x.TopGrade
FROM
(
SELECT
Grades.Student_ID,
MAX(Grades.Grade) AS TopGrade
FROM Grades
GROUP BY Grades.Student_ID
) x
INNER JOIN Grades g
ON g.Student_ID = x.Student_ID AND g.Grade = x.TopGrade
LEFT JOIN Subject s
ON g.Subject_ID = s.Subject_ID
If the same student has two or more marks with exactly the same grade, it will return all Subjects.
Here's my original answer, which will work on SQL Server
SELECT x.Student_ID, x.Subject_Code, x.Grade
FROM
(
SELECT
Grades.Student_ID,
Subject.Subject_Code,
RANK() OVER (PARTITION BY Grades.Student_ID ORDER BY Grades.Grade DESC) AS [Rank],
Grades.Grade
FROM Grades
LEFT JOIN Subject
ON Grades.Subject_ID = Subject.Subject_ID
) x
WHERE x.[Rank] = 1;
SqlFiddle of both the above queries here. In addition, there is an example with ROW_NUMBER with an additional arbitrary ORDER BY to pick one top subject when the student has equal marks in two or more subjects.

Related

Select A column from Subquery

I have a query like below
Select Student_ID, Name, School
From Student S
Where S.Student_ID in ( select associate_id from Details D)
The output count is coming as expected
But now I have a new requirement to get additional column (D.Subject)in the data from the Details table
Select S.Student_ID, S.Name, S.School, D.Subject
From Student S
Where S.Student_ID in ( select associate_id from Details D)
When I’m trying to achieve the above by using the join like below the count is not matching . I tried both Left outer and inner join and the count doesn’t come out correctly .
Select S.Student_ID, S.Name, S.School, D.Subject
From Student S
Left outer join Details D on S.Student_ID = D.associate_id
Where S.Student_ID in ( select associate_id from Details D)
Please let me know how to achieve this
Since we dont have the sample data, I tried to put something together based on your question
problem is with the distinct count.
You should use distinct values in select. Dont really need the where condition, as that will be covered in your join condition.
Select distinct S.Student_ID, S.Name, S.School, D.Subject
From Student S join Details D on S.Student_ID = D.associate_id;
this will give you the unique counts there are. As there may be more join conditions between these tables and not just the column student_id to associate_id
for ex-
If i take the example below
and
in line query will give you a different count then inner join.
So either you find a correct join conditions or distinct the values to keep you close.

Combining FREQUENCY count and INNER CASE in SQL

How do I combine both in order to show the student name (sname) and section number (sectno) for a class that has more than 6 student? So far I have this
SELECT student.sname, enroll.sectno,
FROM student
INNER JOIN enroll
ON student.sid=enroll.sid
with
SELECT grade,
COUNT(grade)AS Frequency
FROM enroll
GROUP BY grade
HAVING COUNT(grade)>6
looks like you were very close. I think the below should work for you:
SELECT
student.sname,
enroll.sectno,
COUNT(enroll.grade) AS Frequency
FROM student
INNER JOIN enroll ON student.sid=enroll.sid
GROUP BY student.sname, enroll.sectno
HAVING COUNT(enroll.grade)>6
In your 2md Select there's no relation to a class that has more than 6 student, IMHO the GROUP BY should be based on column like classid. And then you can simply combine both using a Windowed Aggregate:
with cte as
(
SELECT student.sname, enroll.sectno,
-- get the count per class, might be a different column than sectno
count(*) over (partition by enroll.sectno) as cnt
FROM student
INNER JOIN enroll
ON student.sid=enroll.sid
)
select * from cte
where cnt > 6

Returning the Min() of a Count()

I am studying for an SQL test and the previous year has the final question:
Name the student who has studied the least number of papers. How many
papers have they studied?
So far, this is the select query that I have created:
select min(Full_Name), min(Amount)
from (select st.ST_F_Name & ' ' & st.ST_L_Name as Full_Name, count(*) as Amount
from (student_course as sc
inner join students as st
on st.ST_ID=sc.SC_ST_ID)
group by st.ST_F_Name & ' ' & st.ST_L_Name)
This works perfectly for returning the result I want but I'm not sure if this is the way I should be doing this query? I feel like calling min() on the Full_Name could potentially backfire on me under certain circumstances. Is there a better way to be doing this? (this is in MS Access for unknown reasons)
If you want only 1 of such students if there are multiple, this is probably the simplest:
select st.ST_F_Name, st.ST_L_Name, count(*) as Amount
from student_course as sc
inner join students as st
on st.ST_ID=sc.SC_ST_ID
group by st.ST_ID
order by Amount ASC LIMIT 1
However, if you want to find all stuch students, you follow a different approach. We use a WITH clause to simplify things, that defines a CTE (Common Table Expression) computing the number of courses per-student. And then we select students where their number equals to the minimum in that CTE:
with per_student as (
select st.ST_F_Name, st.ST_L_Name, count(*) as Amount
from student_course as sc
inner join students as st
on st.ST_ID=sc.SC_ST_ID
group by st.ST_ID
)
select * from per_student
where amount = (select min(amount) from per_student)
But the real trick in that question is that there might be students that didn't take ANY courses. But with approaches presented so far you'll never see them. You want something like this:
with per_student as (
select st.ST_F_Name, st.ST_L_Name, count(sc.SC_ST_ID) as Amount
from student_course as sc
right outer join students as st
on st.ST_ID=sc.SC_ST_ID
group by st.ST_ID
)
select * from per_student
where amount = (select min(amount) from per_student)
You can order by count(*) to get the student with the least # of papers:
i.e.
select * from students where st_id in (
select top 1 sc_st_id
from student_course
group by sc_st_id
order by count(*)
)
if you also need the # of papers studied, then join a derived table containing the min count:
select * from students s
left join (
select top 1 sc_st_id, count(*)
from student_course
group by sc_st_id
order by count(*)
) t on t.sc_st_id = s.st_id

SQL count after joining two tables

I am newbie to SQL, I would like to come up with a count, assume this example with 2 tables:
School(schoolID, name,....)
Student(StudentID, SchoolID, ...)
I tried:
SELECT COUNT(studentID)
FROM School s, Student t
WHERE s.schooldID = t.schoolID
How can I get a count of all students across all schools?
Since you have the school ID in the student table, it doesn't appear to me that you need to join to school at all. Just select a count from the student table and group by schoolID:
SELECT schoolID, COUNT(*) AS numStudents
FROM student
GROUP BY schoolID;
The only reason you'd need to join to School is if you want other information, such as school name and so on. If you just want the school id and number of students, the above will work.
To complete that last thought, possibly irrelevant to your question. If you did want school name, you just do an inner join and put school.name in your select statement, along with the count from the student table and group by school ID still:
SELECT s.name, st.COUNT(*) AS numStudents
FROM student st
JOIN school s ON s.id = st.schoolID
GROUP BY s.id;
If you want to get the count per school, you need a group by. Also, usually we prefer ANSI style joins, since in fact all database systems support them nowadays and they easier to read and maintain:
select count(t.studentID)
from Student t
join School s /* added join for your convenience, not necessary here */
on s.schooldID = t.schooldID
group
by t.schoolID

SQL Query Help - Return row in table which relates to another table row with max(column)

I have two tables:
Table1 = Schools
Columns: id(PK), state(nvchar(100)), schoolname
Table2 = Grades
Columns: id(PK), id_schools(FK), Year, Reading, Writing...
I would like to develop a query to find the schoolname which has the highest grade for Reading.
So far I have the following and need help to fill in the blanks:
SELECT Schools.schoolname, Grades.Reading
FROM Schools, Grades
WHERE Schools.id = (* need id_schools for max(Grades.Reading)*)
SELECT
Schools.schoolname,
Grades.Reading
FROM
Schools INNER JOIN Grades on Schools.id = Grades.id_schools
WHERE
Grades.Reading = (SELECT MAX(Reading) from Grades)
Here's how I solve this sort of problem without using a subquery:
SELECT s.*
FROM Schools AS s
JOIN Grades AS g1 ON g1.id_schools = s.id
LEFT OUTER JOIN Grades AS g2 ON g2.id_schools <> s.id
AND g1.Reading < g2.Reading
WHERE g2.id_schools IS NULL
Note that you can get more than one row back, if more than one school ties for highest Reading score. In that case, you need to decide how to resolve the tie and build that into the LEFT OUTER JOIN condition.
Re your comment: The left outer join looks for a row with a higher grade for the same school, and if none is found, all of g2.* columns will be null. In that case, we know that no grade is higher than the grade in the row g1 points to, which means g1 is the highest grade for that school. It can also be written this way, which is logically the same but might be easier to understand:
SELECT s.*
FROM Schools AS s
JOIN Grades AS g1 ON g1.id_schools = s.id
WHERE NOT EXISTS (
SELECT * FROM Grades g2
WHERE g2.id_schools <> s.id AND g2.Reading > g1.Reading)
You say it's not working. Can you be more specific? What is the answer you expect, and what's actually happening, and how do they differ?
edit: Changed = to <> as per suggestion in comment by #potatopeelings. Thanks!
This should do it
select * from Schools as s
where s.id=(
select top(1) id_schools from grades as g
order by g.reading desc)