Display courses with at least 10 students - sql

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.

Related

What is the alternative for ALL and EVERY in SQLite?

Hey im new to SQL and I'm trying to find the names of students enrolled in the maximum number of classes using SQLite on sqliteonline.com
These are the relations:
This is what I have:
SELECT S.sname
FROM Student S
WHERE S.snum IN (SELECT E.snum
FROM Enrolled E
GROUP BY E.snum
HAVING COUNT (*) >= ALL (SELECT COUNT (*)
FROM Enrolled E2
GROUP BY E2.snum ))
AND this is the error that I get:
I'm guessing the ALL keyword doesn't exist in SQLite or is used differently so I'm wondering what I should do next.
I'm also having the same problem with the EVERY keyword.
What I'm trying to solve:
For each faculty member that has taught classes only in room R128, print the faculty
member's name and the total number of classes she or he has taught.
What I have:
SELECT F.fname, COUNT(*) AS CourseCount
FROM Faculty F, Class C
WHERE F.fid = C.fid
GROUP BY F.fid, F.fname
HAVING EVERY ( C.room = "R128" )
What I get:
You can use ORDER BY and LIMIT:
SELECT S.sname
FROM Student S
WHERE S.snum IN (SELECT E.snum
FROM Enrolled E
GROUP BY E.snum
HAVING COUNT(*) = (SELECT COUNT(*)
FROM Enrolled E2
GROUP BY E2.snum
ORDER BY COUNT(*) DESC
LIMIT 1
)
);
Note: There are other ways to express the query logic. This specifically addresses the question that you asked.
One option uses window functions:
select s.name
from student s
inner join (
select snum, rank() over(order by count(*) desc) rn
from enrolled e
group by snum
) e on e.snum = s.snum
where rn = 1
If your version of SQLite does not support window functions (which were added in version 3.25), I would recommend a join and filtering with a having clause:
select s.name
from students s
inner join enrolled e on e.snum = s.snum
group by s.snum, s.name
having count(*) = (
select count(*)
from enrolled
group by snum
order by count(*) desc limit 1
)

SQL Nested Aggregate Function

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;

Selecting exact value in SQL

I am stuck on a query and decided to ask for help here.
I have 2 tables Students and Values. In students I have the Name and in Values I have the grade.
Let's suppose we have 3 students.
**X** 7,8,10
**Y** 6,9,7
**Z** 7
How can i select the students with the grade EXACTLY "7"?
I tried:
SELECT WHERE grade = 7 But it takes in the consideration the students who have "7" but also other grades too.
I think this problem is tricky, can someone give a hint?
One way is to use a comparison between unconditional and conditional counts:
select s.student_name
from students s join grades g on s.student_id = g.student_id
group by student_id
having count(*) = count(case when g.value = 7 then 1 end)
;
(guessing at some column names along the way)
How it works: After joining the two tables, the rows are grouped by student_id. Then COUNT(*) counts all the grades, and the conditional count counts the grades equal to 7. The query returns the student names when the two counts are equal (meaning all the grades are 7).
Another solution (less efficient):
select s.student_name
from students s inner join grades g on s.student_id = g.student_id
where g.grade = 7
minus
select s.student_name
from students s inner join grades g on s.student_id = g.student_id
where g.grade != 7 or g.grade is null
;
One simple way is to look at the task like this: Find students that have no grade other than seven.
select *
from students
where not exist
(
select *
from grades
where grades.student_id = students.student_id
and grades.grade <> 7
);
or
select *
from students
where student_id not in
(
select student_id
from grades
where grade <> 7
);
I think that an INNER JOIN followed by an exclusive LEFT JOIN would do the trick. Something like this:
WITH
seven AS (
SELECT a.id, a.name, b.grade
FROM student_name a
INNER JOIN student_grade b
ON a.id = b.id
WHERE b.grade = '7'
)
, not_seven AS (
SELECT a.id, a.name, a.grade
FROM seven a
LEFT JOIN student_grade
ON a.id = b.id
WHERE b.id IS NULL
AND b.grade <> '7'
)
SELECT * FROM not_seven;
what about using count and sum? Would that work?
select s.student, Count(v.grade), sum(v.grade)
From student as s
join values as v on v.student = s.student
group by student
having sum(v.grade) = 7 and count(v.grade) = 1

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)