Combining FREQUENCY count and INNER CASE in SQL - 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

Related

Why don't the values for a single studentid add up while i execute this SQL statement even though i used Distinct?

I have to write a query to display the studentid and the total fees paid by each student and then sort the result based on studentid in ascending order.
I have used Distinct so that studid is displayed only once but still I don't get the expected output.
My code:
select distinct s.studid,c.fees as total_fees from Student s join
Registration r on s.studid=r.studid join Course c on r.courseid=c.courseid
group by s.studid,c.fees order by s.studid;
My Output:
Expected Output:
You join registration.
That would give you one record per student and registration,
which is what you want.
Calling distinct on it then, though, means you only get one of those records.
What you want to do is sum up those fees, group the results per student, and order by studid.
SELECT s.studid, SUM(c.fees) as total_fees FROM student s
JOIN registration r ON s.studid = r.studid
JOIN course c ON r.courseid=c.courseid
GROUP BY s.studid
ORDER BY s.studid;
You are querying by distinct combination of the student ID and the fees instead of summing the fees:
SELECT s.studid, SUM(c.fees) as total_fees
FROM student s
JOIN registration r ON s.studid = r.studid
JOIN course c ON r.courseid=c.courseid
GROUP BY s.studid
ORDER BY s.studid ASC

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

Problems selecting columns with aggregates (SQL Server)

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.

How to retrieve results for top items

I have ran a query to give me the total number of students within each school but now I need to know the name of those students within each school while keeping the top result by total number at the top. How can I add to this query to show me the names of the students?
Here is what I have to show me the total number of students at each school:
SELECT
dbo_Schools.Schools,
Count(dbo_tStudent.Student) AS NumberOfStudents
FROM
dbo_tStudent
INNER JOIN dbo_tSchools ON dbo_tStudent.SchoolID=dbo_tSchool.SchoolID
GROUP BY dbo_tSchool.School
ORDER BY Count(dbo_tStudent.Student) DESC;
Its important that I keep the schools in order from top number of students while listing the students
In this case you could use a Sub Query to achieve your resultset.
To use order by inside a subquery, you will also need a top or limit operator.
SELECT sc.schoolname
,st.columns...
FROM dbo_tStudent st
INNER JOIN (
SELECT TOP 1000 dbo_Schools.SchoolID
,min(schoolname) schoolname
,Count(dbo_tStudent.Student) AS NumberOfStudents
FROM dbo_tStudent
INNER JOIN dbo_tSchools ON dbo_tStudent.SchoolID = dbo_tSchools.SchoolID
GROUP BY dbo_tSchool.School
ORDER BY Count(dbo_tStudent.Student) DESC
) sc ON st.SchoolID = sc.SchoolID
Assuming that you are using SQL Server, you can use a CTE to join the first aggregate with the details like this:
;WITH cte as (
SELECT TOP 1000 dbo_Schools.SchoolID, Count(dbo_tStudent.Student) AS NumberOfStudents
FROM
dbo_tStudent
INNER JOIN dbo_tSchools ON dbo_tStudent.SchoolID = dbo_tSchools.SchoolID
GROUP BY dbo_tSchool.School
ORDER BY Count(dbo_tStudent.Student) DESC
)
SELECT
sc.<your school name column>,
st.<your student columns>
from
dbo_tStudent st
INNER JOIN cte ON st.SchoolID = cte.SchoolID
INNER JOIN dbo_tSchools sc on cte.SchoolID = sc.SchoolID
More generally speaking: you need a derived table (your aggregation containing the group by clause) that is joined with the select statement for the student details. In this example, the CTE basically is a SQL Server feature that facilitates the use of derived tables.

Repeated use of an alias within a join

I am trying to append a column created by using COUNT(*) and GROUP BY to the original selection that was counted. However, the selection is pretty complex (much more than the WHERE ... line I've included in my example) so I'd rather not duplicate the code.
SQL Server doesn't approve of my using an alias t1 inside of the left join statement. Any suggestions?
select t1.school, t1.grade,t1.individual,t2.cnt as 'class size' from (
select * from students
where (students.age < 16 and students.ACT_score is not null)
) as t1
left join (
select distinct school, grade, count(*) as 'cnt' from t1
group by school, grade
) as t2
on t1.school = t2.school and t1.grade = t2.grade
If it's 2005 or newer, use a CTE:
;WITH MyCTE AS
(
<Your complicate query here>
)
SELECT fields
FROM MyCTE
JOIN (subquery referencing MyCTE)
...
It might be easier to maintain if you use COUNT(*) with an OVER clause as follows:
with cntAppended as (
select
*,
count(*) over (partition by school, grade)
from students
)
select
school,
grade,
individual,
cnt as "class size"
from cntAppended
where (age < 16 and ACT_score is not null)
Don't be tempted to avoid WITH and putting the WHERE clause in one query with COUNT. If you do, you will only count students in each school and grade who are < 16 and have ACT scores. It looks like you want to count all students for the [class size] column, but only see data for certain ones in your result.
If and when T-SQL supports the QUALIFY keyword, queries like this may be even easier:
select
school,
grade,
individual,
count(*) over (partition by school, grade) as "class size"
from students
QUALIFY (age < 16 and ACT_score is not null)
I rewrote your query in a simpler form, no CTE needed:
SELECT t1.school
,t1.grade
,t1.individual
,t2.cnt AS 'class size'
FROM students t1
LEFT JOIN (
SELECT school
,grade
,count(*) AS 'cnt'
FROM students
GROUP BY school, grade
) AS t2 ON t1.school = t2.school
AND t1.grade = t2.grade
WHERE t1.age < 16
AND t1.ACT_score IS NOT NULL