Using WHERE, HAVING, and GROUP BY in the same SQL query - sql

I'm trying to find the names of all classes that either meet in room R128 or have five or more students enrolled in these two tables called 'enroll' and 'class'. I can find the two parts of this question individually, but I don't understand how I can find them both in one query.
This gives me the classes in room R128 that I want:
SELECT class.cname
FROM enroll RIGHT JOIN class ON enroll.cname=class.cname
WHERE room='R128';
and this gives me the classes with 5 or more students enrolled in them:
SELECT class.cname
FROM enroll RIGHT JOIN class ON enroll.cname=class.cname
GROUP BY enroll.cname
HAVING COUNT(enroll.cname)>4;
I can't figure out how to combine them into one query. This is what I have so far, but it returns an empty set:
SELECT class.cname, COUNT(enroll.cname)
FROM enroll RIGHT JOIN class ON enroll.cname=class.cname
WHERE room='R128'
GROUP BY enroll.cname
HAVING COUNT(enroll.cname)>4;
Here are the class table
and the enroll table
The classes in room R128 are Patent Law, Data Structures, Archaeology of the Incas, Dairy Herd Management, and Introduction to Math. The classes with 5 or more students are Database Systems and Operating System Design.

Does this produce the result you want?
select c.cname
from class c
inner join enroll e on e.cname = c.cname
group by c.cname
having max(room = 'R128') = 1 or count(*) > 4

Assuming that room is in class, I would suggest union:
select c.cname
from class c
where room = 'R128'
union -- on purpose to remove duplicates
select e.cname
from enroll e
group by e.cname
having count(*) >= 5;

Related

Getting data for each Student from other tables

I am trying to get information for each student in a database. I know that there are exactly 4 students and that between all students, there are 6 enrollments (ie. some students are enrolled in multiple courses). Therefore, the proper output would have 6 rows, all containing the necessary student info. There would be duplicate students in the returned query. I am able to join the students and the enrollments just fine and end up with the 6 total enrollments. However, once I join in the other tables to get data about the courses that the students are enrolled in, I end up getting more and more rows. Depending on how I format my query, I get between 7-11 rows. All that I want is the 6 rows that correspond to the enrollments and nothing more. Why does that happen like this and how do I fix it?
I have tried different kinds of joins, unions, intersections, and have been working at the question for well over an hour. This is what I have currently:
Select s.sid, e.term, c.cno, e.secno, ca.ctitle
from Students as s
join Enrolls as e
on s.sid = e.sid
join Courses as c
on e.secno = c.secno
join Catalogue as ca
on ca.cno = c.cno
question details
database details
It looks like the Courses and Enrollment tables have what we call 'a composite key'. I bet you must join the c and e tables with both term and secno columns.
Your query mus be like this:
SELECT s.sid, e.term, c.cno, e.secno, ca.ctitle
FROM Students AS s
JOIN Enrolls AS e ON s.sid = e.sid
JOIN Courses AS c ON e.secno = c.secno AND e.term = c.term
JOIN Catalogue AS ca ON ca.cno = c.cno
When you have a composite key and uses only one of the columns to join, you will get unwanted rows from the foreign table, making a Cartesian product result

SQLITE3 Inner Join Returning Too Many Tuples

I have 4 tables in sqlite3:
students(student_id, student_name)
instructors(instructor_id, instructor_name)
courses(course_id, course_name)
and
enrollments(enroll_id, student_id, instructor_id, course_id)
With the last 3 columns having a foreign key reference to the relevant columns in the first 3 tables
When I try to do a query that shows only the student’s name, instructor’s name and course name where an enroll_id is equal to 001 for example
SELECT students.student_name, instructors.instructor_name, courses.course_name
FROM users, instructors, courses INNER JOIN enrollments
WHERE enrollments.enroll_id = 001;
I'm getting a lot of data from all tables, but not a single tuple with just the relevant enroll data assigned to enroll_id 001. Any help is appreciated
Never use commas in the FROM clause. Always use proper, explicit JOIN syntax. The right way to express this query is:
SELECT s.student_full_name, i.instructor_name, c.course_name
FROM enrollments e JOIN
instructors i
ON e.instructor_id = i.instructor_id JOIN
courses c
ON e.course_id = c.course_id JOIN
students s
ON e.student_id = s.student_id
WHERE e.enroll_id = 001;
The use of table aliases is highly recommended. It makes the query easier to write and to read.
Try this:
SELECT students.student_full_name, instructors.instructor_name, courses.course_name
FROM users, instructors, courses, enrollments
WHERE enrollments.enroll_id = 001
and enrollments.student_id = users.student_id
and enrollments.instructor_id = instructors.instructor_id
and enrollments.course_id = courses.course_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 Count and Group by - Do count and group, used together, allow for this?

i'm working on an exercise query, using standard SQL.
What I need, in this example, is get the Name and the ID of every student who has enrolled in any course more than 2 times. In this case, the tables have the following information (I'll list only the columns that matter for this exercise):
Student has the ID and the name of the student.
Course has the course ID.
Class has the group ID and the course ID.
Registration has the reg. ID, the student ID, the class ID and the grade obtained.
So, it all sums up to find out which students have more than 2 entries in Registration that are matched to the same course, via Class. So far I've gotten this:
SELECT Student.id, Student.name FROM
Student S JOIN Registration R on S.id = R.studentID
WHERE (SELECT COUNT(*) FROM
Course C JOIN Class L on L.courseId = C.id
JOIN Registration R on R.classId = L.id
group by C.id ) > 2
The question is, do Count and Group by work this way?? Do they allow me to get the amount of matches on each group, or do they just give me the results on the set, as I fear they do?
If so, any ideas on how may I approach this problem??
Thanks for the help!!
Start reading up on the HAVING clause. Also, you aliases Student as S, so you need to use the prefix "S" for the SELECT clause, not "Student"
SELECT S.id StudentID, S.name, C.id CourseID
FROM Student S
JOIN Registration R on S.id = R.studentID
JOIN Class L on R.ClassId = L.id
JOIN Course C on L.courseId = C.id
GROUP BY S.id, S.name, C.id
HAVING COUNT(R.studentID) > 2;
All 4 tables are joined to produce a resultset representing all students registrated for any class of a course. Then we GROUP BY the student-course combination, and find out the ones where there are more than 2 registrations using the HAVING clause. Of course, if you meant "2 or more" instead of "more than 2", you'd use > 1 or >= 2 instead of > 2.

Can I include a non-aggregated Column in an aggregate function in SQL without putting it into a GROUP BY clause?

Take the following tables...
Classes
ClassId ClassName
1 Math
2 Math
3 Science
4 Music
Registrations
RegistrationId ClassId StudentName
1 1 Stu
2 1 Rick
3 2 John
4 4 Barb
5 4 Dan
6 3 Einstein
Yes, there are 2 classes with the same name (Math) as they may be at different times. I would like to get a list of the classes and the number of students registered for each one. I would like the following columns (ClassId, ClassName, StudentCount).
My attempt at this would be something along the lines of...
SELECT Classes.ClassId, Classes.ClassName, Count(Registrations.RegistrationId)
FROM Classes
INNER JOIN Registrations ON Classes.ClassId = Registrations.ClassId
GROUP BY Classes.ClassId
(Note I would like to GroupBy the ClassId but NOT ClassName). Is this possible in SQLServer 2008? Obviously I ask because SQL complains
"ClassName is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause."
Thanks!
No, SQL Server does not allow you to omit columns from the GROUP BY that are not wrapped in aggregate functions. There's no harm in including the class name, because the group by will be performed on the combination of the group by columns:
SELECT c.classid,
c.classname,
COUNT(r.registrationid)
FROM CLASSES c
JOIN REGISTRATIONS r ON r.classid = c.classid
GROUP BY c.classid, c.classname
You could derive a table based on the counting, using:
SELECT c.classid,
c.classname,
r.num
FROM CLASSES c
JOIN (SELECT t.classid,
COUNT(*) AS num
FROM REGISTRATIONS t
GROUP BY t.classid) r ON r.classid = c.classid
There should be no harm in including Classes.ClassName in your GROUP BY statement. You'd be grouping by the distinct pairs of ClassId and ClassName so (1, 'Math') and (2, 'Math') are still two distinct groupings.
You can either put the ClassName in the group by clause, which will be ok because it is a 1-to1 with the ClassID:
SELECT Classes.ClassId, Classes.ClassName, Count(Registrations.RegistrationId)
FROM Classes
INNER JOIN Registrations ON Classes.ClassId = Registrations.ClassId
GROUP BY Classes.ClassId, Classes.ClassName
or put a MAX(ClassName) in the select clause. Either one will yield the same result.
No, you can't: it's a contradiction.
GROUP BY = collapse to discrete values. If you don't collapse, you need to aggregate it.
As it happens, you'd get the same result anyway because ClassName depends on ClassID.