SQL Nested Aggregate Function - sql

I have tables:
Student(sID, firstName, lastName, email, cgpa)
Course(cNum, name, dept, credit)
Offering(oID, cNum, dept, year, term, instructor)
Took(sID, oID, grade)
I'm trying to complete the question:
Find all courses for the term 2017F and the current enrolment
I currently have this query to grab the number of students enrolled in each course:
SELECT Took.oID, COUNT(*) AS enrolment
FROM Took
GROUP BY Took.oID
HAVING COUNT(*) > 0
Nested inside of this statement to grab the correct courses that I want the enrolment counts for:
SELECT oID
FROM Offering
WHERE Offering.year = 2017
AND Offering.term = 'F'
Both of which are nested inside of this query to tie everything together:
SELECT DISTINCT Offering.cNum, Course.name, (I WOULD LIKE COUNT(*) AS enrolment HERE)
FROM Offering NATURAL JOIN Course
WHERE Offering.oID IN (
SELECT oID
FROM Offering
WHERE Offering.year = 2017
AND Offering.term = 'F'
AND oID IN (
SELECT Took.oID, COUNT(*) AS enrolment
FROM Took
GROUP BY Took.oID
HAVING COUNT(*) > 0))
GROUP BY Offering.cNum, Course.name;
My question is, how can I pass the resulting COUNT(*) AS enrolment from the furthest nested query to the initial query so that it can be displayed in the resulting projection? (This is homework)

If I understand correctly you can try to use a subquery in from with JOIN instead of where subquery.
Then you can get count column from the subquery.
SELECT DISTINCT Offering.cNum, Course.name,t1.enrolment
FROM Offering
JOIN (
SELECT Took.oID,
COUNT(*) AS enrolment
FROM Took
GROUP BY Took.oID
HAVING COUNT(*) > 0
) t1 on t1.oID = Offering.oID
NATURAL JOIN Course
WHERE Offering.year = 2017 AND Offering.term = 'F'

Try this
SELECT c.*
, (
SELECT COUNT(*)
FROM Took
WHERE oID = o.oID
) AS theCount
FROM Course c
JOIN Offering o ON o.cNum = c.cNum
WHERE o.year = 2017 AND o.term = 'F'

May be this
SELECT Course.name, Course.cNum, count(*) as enrolment
FROM Course
JOIN Offering ON Course.cNum = Offering.cNum
JOIN Took ON Offering.oID = Took.oID
WHERE Offering.year = 2017
AND Offering.term = 'F'
GROUP BY Course.name, Course.cNum
HAVING count(*) > 0;

Related

Display courses with at least 10 students

I have the following tables:
Students (id, name, surname, study_year, department_id)
Courses(id, name)
Course_Signup(id, student_id, course_id, year)
I want to display the courses to which at least 10 students have signed up for, only using subqueries (no group-by, join or set operations). This could be easily implemented using data aggregation and join:
SELECT c.name, COUNT(csn.course_id)
FROM Course_Signup csn
JOIN Courses c
ON csn.course_id = c.id
GROUP BY c.name
HAVING COUNT(csn.course_id) >= 10
But how would I do this only using subqueries? Is there any other way, other than COUNT, to get the number of courses? Thank you, in advance!
You can use a correlated sub-query to retrieve the name:
SELECT (SELECT c.name FROM Courses c WHERE csn.course_id = c.id) AS name,
COUNT(*)
FROM Course_Signup csn
GROUP BY
course_id
HAVING COUNT(*) >= 10
Note: you should also GROUP BY the primary key the uniquely identifies the course as there may be two courses with the same name.
If you also don't want to use GROUP BY then:
SELECT name
FROM Courses c
WHERE 10 <= ( SELECT COUNT(*)
FROM Course_Signup csn
WHERE csn.course_id = c.id )
or, to also get the number of sign-ups:
SELECT *
FROM (
SELECT name,
( SELECT COUNT(*)
FROM Course_Signup csn
WHERE csn.course_id = c.id ) AS num_signups
FROM Courses c
)
WHERE num_signups >= 10;
You could do:
SELECT c.name
FROM Courses c
WHERE (
SELECT COUNT(*)
FROM Course_Signup csn
WHERE csn.course_id = c.id
) >= 10
which only uses a subquery and has no group-by, join or set operations.
fiddle
If you wanted the actual count in the result set then you would need to repeat the subquery in the select list.
You might also need to do COUNT(DISTINCT cs.student_id) if there might be duplicates; particularly if the same student can sign up in multiple years - but then you might want to restrict to a single year anyway.

Find the sailors that have been on EVERY boat

I don't know how to explain the problem in a generic way so i'll post the specific case:
i have 3 tables:
Sailors:
S(ids, names, rating, age)
Boats:
B(idb, nameb, color)
Bookings:
Bo(ids, idb, date)
i have to write a query that finds all the sailors who have booked EVERY boat.
Even if i posted a specific case i'd like a generic answare that can be applied to every problem of tha same kind.
thank you in advance.
You can get the sailors's ids who have booked every boat with this query:
select ids
from bookings
group by ids
having count(distinct idb) = (select count(*) from boats)
So use it either with the operator IN:
select * from sailors
where ids in (
select ids
from bookings
group by ids
having count(distinct idb) = (select count(*) from boats)
)
or join it to sailors:
select s.*
from sailors s
inner join (
select ids
from bookings
group by ids
having count(distinct idb) = (select count(*) from boats)
) t on t.ids = s.ids
You can use sum with in:
select * from sailors s1 group by ids having (select sum(idb in (select b2.idb from bookings b2 where b2.ids = s1.id)) from boats) = (select count(*) from boats)

SQL query to find students who received an A for every course taken

I have following schema:
Students(sid, firstname, lastname, status, gpa, email)
Courses(dept_code, course#, title)
Classes(classid, dept_code, course#, sect#, year, semester, limit, class_size)
Enrollments(sid, classid, lgrade)
I need some help to find out all the students who received an A for every course taken.
I might suggest doing this with an aggregation:
select e.sid
from enrollement e
group by e.sid
having min(lgrade) = max(lgrade) and min(lgrade) = 'A';
try this
select * from students
where sid not in (select distinct sid from enrollement where coalesce (lgrade,'X') <> 'A')
It means: take all students where none of his/her grade is other than A
if you wanna to get also the name of the class and course, you have to join also both tables
Try not to overthink this:
SELECT s.*
FROM STUDENTS s
WHERE s.GPA = 4.0
That'll work for the A=4, B=3, C=2, D=1, F=0 case (standard American grading system).
For non-standard systems (such as my kids high school where A=5 for honors and advanced placement classes) we can't trust the GPA:
SELECT s.*
FROM STUDENTS s
INNER JOIN (SELECT SID, COUNT(*) AS CLASS_COUNT
FROM ENROLLMENTS
GROUP BY SID) cc
INNER JOIN (SELECT SID, COUNT(*) AS A_GRADE_COUNT
FROM ENROLLMENTS
WHERE LGRADE = 'A'
GROUP BY SID) ag
ON ag.SID = s.SID
WHERE CLASS_COUNT = A_GRADE_COUNT
Best of luck.
I think this is the clearest:
select e.sid
from enrollement e
group by e.sid
having count(case when lgrade = 'A' then 1 else 0 end) = count(*) and count(*) > 0

Select one record from two tables in Oracle

There are three tables:
A table about students: s41071030(sno, sname, ssex, sage, sdept)
A table about course: c41071030(cno, cname, cpno, credit)
A table about selecting courses: sc41071030(sno, cno, grade)
Now, I want select the details about a student whose sdept='CS' and he or she has selected the most courses in department 'CS'.
As with any modestly complex SQL statement, it is best to do 'TDQD' — Test Driven Query Design. Start off with simple parts of the question and build them into a more complex answer.
To find out how many courses each student in the CS department is taking, we write:
SELECT S.Sno, COUNT(*) NumCourses
FROM s41071030 S
JOIN sc41071030 SC ON S.Sno = SC.Sno
WHERE S.Sdept = 'CS'
GROUP BY S.Sno;
We now need to find the largest value of NumCourses:
SELECT MAX(NumCourses) MaxCourses
FROM (SELECT S.Sno, COUNT(*) NumCourses
FROM s41071030 S
JOIN sc41071030 SC ON S.Sno = SC.Sno
WHERE S.Sdept = 'CS'
GROUP BY S.Sno
)
Now we need to join that result with the sub-query, so it is time for a CTE (Common Table Expression):
WITH N AS
(SELECT S.Sno, COUNT(*) NumCourses
FROM s41071030 S
JOIN sc41071030 SC ON S.Sno = SC.Sno
WHERE S.Sdept = 'CS'
GROUP BY S.Sno
)
SELECT N.Sno
FROM N
JOIN (SELECT MAX(NumCourses) MaxCourses FROM N) M
ON M.MaxCourses = N.NumCourses;
And we need to get the student details, so we join that with the student table:
WITH N AS
(SELECT S.Sno, COUNT(*) NumCourses
FROM s41071030 S
JOIN sc41071030 SC ON S.Sno = SC.Sno
WHERE S.Sdept = 'CS'
GROUP BY S.Sno
)
SELECT S.*
FROM s41071030 S
JOIN N ON N.Sno = S.Sno
JOIN (SELECT MAX(NumCourses) MaxCourses FROM N) M
ON M.MaxCourses = N.NumCourses;
Lightly tested SQL: you were warned. To test, run the component queries, making sure you get reasonable results each time. Don't move on to the next query until the previous one is working correctly.
Note that the courses table turns out to be immaterial to the query you are solving.
Also note that this may return several rows if it turns out there are several students all taking the same number of courses and that number is the largest number that any student is taking. (So, if there are 3 students taking 7 courses each, and no student taking more than 7 courses, then you will see 3 rows in the result set.)
Aggregate sc41071030 rows to get the counts.
Join the results to s41071030 to:
filter rows on sdept;
get student details;
RANK() the joined rows on the count values.
Select rows with the ranking of 1.
WITH
aggregated AS (
SELECT
sno,
COUNT(*) AS coursecount
FROM
sc41071030
GROUP BY
sno
),
ranked AS (
SELECT
s.*,
RANK() OVER (ORDER BY agg.coursecount DESC) AS rnk
FROM
s41071030 s
INNER JOIN aggregated agg ON s.sno = agg.sno
WHERE
s.sdept = 'CS'
)
SELECT
sno,
sname,
ssex,
sage,
sdept
FROM
ranked
WHERE
rnk = 1
;

Count within a count

REVISED:
Okay, thanks to all of your input, I figured out what I was doing wrong (sorry guys). I am grabbing the courseID which counts as a section NOT an actual course. For me to grab the actual courseName, I have to go over to the Course Table.
So now I have StudentID from Enrollment and CourseNum from Course that need to be used to count. I'll try and work with what you guys have provided to see if I can come up with the results.
EDIT:
Here's my revised SQL. This provides me with the total courses for each student. I'm getting there:
SELECT Count(DISTINCT Course.courseNum), Grades.studentID
FROM Grades INNER JOIN
Course ON Grades.courseID = Course.courseID
GROUP BY Grades.studentID;
Final Code just in case those who care:
SELECT COUNT(NumCourses) FROM
(SELECT Count(DISTINCT Course.courseNum)AS NumCourses
FROM Grades INNER JOIN
Course ON Grades.courseID = Course.courseID
GROUP BY Grades.studentID
HAVING Count(DISTINCT Course.courseNum) = 1) a;
Try this:
select count(studentId), count(courseId) from enrolment group by courseId having (count(courseId) = 2);
To get the total number of students on 4+ courses, this is the SQL:
SELECT COUNT(CourseCount) AS CourseCount
FROM (
SELECT StudentID, COUNT(CourseID) AS CourseCount
FROM enrollment
GROUP BY StudentID
HAVING (COUNT(CourseID) >= 4)) AS T
It is much simpler to get the student count using a second query.
SELECT COUNT(NumCourses) AS NumStudents, NumCourses FROM (SELECT COUNT(courseID) AS NumCourses FROM enrollment GROUP BY courseID HAVING COUNT(courseID) = 4) As SomeTableAlias
select count(*)
from (select student_id from enrollment group by student_id having count(*) = 4)
The inner query gives you the IDs of the students who enrolled in exactly 4 courses then I count them.
I thought you wanted the number of students who are enrolled in exactly four courses.
If you want to count the number of students and the number of courses, do this:
SELECT * FROM (SELECT COUNT(*) AS NumStudents FROM (SELECT DISTINCT studendid FROM enrollment)), (SELECT COUNT(*) AS NumCourses FROM (SELECT DISTINCT courseid FROM enrollment))
SELECT COUNT(*) FROM
(SELECT COUNT(*) FROM enrollment
GROUP BY studentid HAVING COUNT(*) = 4)