Getting data for each Student from other tables - sql

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

Related

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

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;

I need an JPA/SQL expert: EXISTS query on an Inner Join returns wrong result

I have three tables and want to:
Select all students from the first table,
that have at least one connection to the school in district '999' in the second table
and at least one connection to the teacher with social_number '101'
and at least one to the teacher with number '103' in the third table.
The tables are connected through the second table.
I created an online sql compiler to show the problem:
http://tpcg.io/FIoO79xi
This query works fine and as expected, until I add the third EXISTS Command where I search for a connection to teacher '103'. Then it doesn't return student A anymore, altough he has a connection to teacher '103'
I found a workaround by adding joins in the Exists sub-query:
http://tpcg.io/0sza7t5g
but since my real database tables have many million entries, this would lead to joining the three tables in every row that the sub-query goes through and this can take very long if it only finds a fitting entry at the end of the table.
I think the problem is here at the sub-query: WHERE th1.school_id = th.school_id where I'm trying to find a connection from the third table teacher to the at the beginning joined together table. If I search for a connection to teacher 102 instead of 103, the query works and returns student A:
http://tpcg.io/2tHIEk3V
Because teacher 101 and 102 have the same school_id.
But how can I write that differently so that the query also finds student A when I search for a connection to teacher 101 and 103? Student A has a connection to both, so it should be possible somehow with exists...
Add: I can't use three seperate queries and then use the Intersect command on them, since I'm translating that SQL into a JPA query. JPAdoesn`t know intersect...
The 1st condition:
at least one connection to the school in district '999' in the second
table
needs a join of student to school.
The 2nd and 3rd conditions:
at least one connection to the teacher with social_number '101'
and at least one to the teacher with number '103'
need 2 separate joins of student to school and teacher:
SELECT s.name
FROM student s
INNER JOIN school sc on s.student_id = sc.student_id AND sc.district = 999
INNER JOIN school sc1 on s.student_id = sc1.student_id
INNER JOIN teacher t1 on t1.school_id = sc1.school_id AND t1.social_number = 101
INNER JOIN school sc2 on s.student_id = sc2.student_id
INNER JOIN teacher t2 on t2.school_id = sc2.school_id AND t2.social_number = 103
Note that a condition like social_number in (101, 103) will not work because it would return results even if only 1 of the conditions was satisfied.
This is why you need 2 joins to school and teacher.
Also all the joins must be inner because you want to satisfy all 3 conditions.
See the demo.
Results:
| name |
| ---- |
| A |
You need 2 teacher table joins
SELECT name
FROM student
left JOIN school sc1 on #student.student_id = sc1.student_id
left JOIN teacher th1 on sc1.school_id = th1.school_id and th1.social_number=101
left JOIN teacher th2 on sc1.school_id = th2.school_id and th1.social_number=103
where sc1.district=999
Why so complicated ?
`SELECT name
FROM student
LEFT JOIN school sc1 on student.student_id = sc1.student_id
LEFT JOIN teacher th1 on sc1.school_id = th1.school_id
WHERE sc1.district = '999'
AND th1.social_number in('101','103')`
doesn't do the trick ?
http://tpcg.io/eGeWjkOf
The whole joining is unnecessary when we ask for a connection between the teacher and the student in an additional WHERE clause.
The Exists subquery then looks like this, instead of a Join I use an additional where to make sure the teacher.school_id is the same as the school.school_id of the school the student goes to:
EXISTS (
SELECT *
FROM teacher th
WHERE th.social_number = '103'
AND th.school_id in (SELECT school_id FROM school WHERE student_id = student.student_id ))

SQL- Find the students who are missing their grades in database

I have three tables in my database. the first one is students which has student_id as primary key. the second one is courses table which has course_id as primary key and at last I have a grades table which has id_student and id_course as foreign keys, and a grade field. I want to the get details of students, who are missing their grades in grades table.
I've searched on stack overflow but could not find the exact answer i am looking for. and what i have tried so far is this following query:
select st.student_id,
st.lname,
st.fname,
cs.course_id,
g.grade
from students st
join grades g
on g.id_student = st.student_id
join courses cs
on cs.course_id = g.id_course
where g.grade is null
If I try this same query without where condition i get 39 rows but i should get 40 because in my database there is one student who is missing grades in course_id 20.
** the missing thing is student_id, where course_id is 20 and grade doesnt exist for it.**
I think the join on grades requires two keys. If I assume that students should be taking all courses, then this would look like:
select st.student_id, st.lname, st.fname, cs.course_id,
g.grade
from students st cross join
courses c left join
grades g
on g.id_student = st.student_id and
g.id_course = c.course_id
where g.grade is null;
The CROSS JOIN generates all combinations of students and courses. The LEFT JOIN/WHERE filters out the ones with missing grades.
Because you use JOIN you are only getting the rows that exist in all 3 tables.
If you change the JOINS to LEFT OUTER JOIN you will see NULLS for the student without a grade.
See https://www.w3schools.com/sql/sql_join.asp
and many other links on the web
SELECT * FROM
(
select st.student_id,st.lname,st.fname,cs.course_id,g.grade
from students st
LEFT OUTER join grades g on g.id_student = st.student_id
LEFT OUTER join courses cs on cs.course_id = g.id_course
)SUB_Q WHERE SUB_Q.grade IS NULL
The inner query should return all students and the ones without a grade should have a grade of null in that. So the WHERE clause of the outer query should filter down to just those.
The join function for the grades table is only looking for results that match both the grades table and students table. Try a full outer join here to include all fields regardless of if they match on both tables. See code below:
SELECT
st.student_id, st.lname, st.fname, cs.course_id, g.grade
FROM students st
FULL OUTER JOIN grades g
ON g.id_student = st.student_id
FULL OUTER JOIN courses cs
ON cs.course_ID = g.id_course
WHERE g.grade IS NULL

Query to find students that failed all given subjects

I'm trying to find the students that have failed every subject in a set of subjects via PostgreSQL queries.
Students fail a subject if they have a not null mark < 50 for at least one course offering of the subject. And I want to find the students that have failed all subjects in the set of subjects Relevant_subjects.
NOTE: students can have several records per course.
SELECT People.name
FROM
Relevant_subjects
JOIN Courses on (Courses.subject = Relevant_subjects.id)
JOIN Course_enrolments on (Course_enrolments.course = Courses.id)
JOIN Students on (Students.id = Course_enrolments.student)
JOIN People on (People.id = Students.id)
WHERE
Course_enrolments.mark is not null AND
Course_enrolments.mark < 50 AND
;
With the code above, I get the students that has failed any of the Relevant_subjects but I my desired result is to get the students that has failed all Relevant_subjects. How can I do that?
Students fail a subject if they have a not null mark < 50 for at least one course offering of the subject.
One of many possible ways:
SELECT id, p.name
FROM (
SELECT s.id
FROM students s
CROSS JOIN relevant_subjects rs
GROUP BY s.id
HAVING bool_and( EXISTS(
SELECT -- empty list
FROM course_enrolments ce
JOIN courses c ON c.id = ce.course
WHERE ce.mark < 50 -- also implies NOT NULL
AND ce.student = s.id
AND c.subject = rs.id
)
) -- all failed
) sub
JOIN people p USING (id);
Form a Carthesian Product of students and relevant subjects.
Aggregate by student (s.id) and filter those who failed all subjects in the HAVING clause with bool_and()over a correlated EXISTS subquery testing for at least one such failed course for each student-subject combination.
Join to people as final cosmetic step to get student names. I added id to get unique results (as names probably are not guaranteed to be unique).
Depending on actual table definition, your version of Postgres, cardinalities and value distribution, there may be (much) more efficient queries.
It's a case of relational-division at its core. See:
How to filter SQL results in a has-many-through relation
And the most efficient strategy is to eliminate as many students as possible as early in the query as possible - like by checking the subject with the fewest failing students first. Then proceed with only the remaining students etc.
Your case adds the specific difficulty that the number and identities of subjects to be tested are unknown / dynamic. Typically, a recursive CTE or similar offers best performance for this kind of problem:
SQL query to find a row with a specific number of associations
I would use aggregation:
SELECT p.name
FROM Relevant_subjects rs JOIN
Courses c
ON c.subject = rs.id JOIN
Course_enrolments ce
ON ce.course = c.id JOIN
Students s
ON s.id = ce.student JOIN
People p
ON p.id = s.id
WHERE ce.mark < 50
GROUP BY p.id, p.name
HAVING COUNT(*) = (SELECT COUNT(*) FROM relevant_subjects);
Note: This version assumes that students only have one record per course and relevant_subjects has no duplicates. These can easily be handling using COUNT(DISTINCT) if necessary.
To handle duplicates, this would look like:
SELECT p.name
FROM Relevant_subjects rs JOIN
Courses c
ON c.subject = rs.id JOIN
Course_enrolments ce
ON ce.course = c.id JOIN
Students s
ON s.id = ce.student JOIN
People p
ON p.id = s.id
WHERE ce.mark < 50
GROUP BY p.id, p.name
HAVING COUNT(DISTINCT rs.id) = (SELECT COUNT(DISTINCT rs2.id) FROM relevant_subjects rs2);

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;