I have two tables named courses and teachers. I need to find the list of teachers who don't take any courses.
This is the query I wrote for getting the teacher who took the most numbered courses -
SELECT t.name AS teacher_name
, COUNT(c.teacher_id) AS courses_taken
FROM courses c
JOIN teachers t
ON c.teacher_id = t.id
GROUP
BY c.teacher_id
ORDER
BY courses_taken DESC
LIMIT 1;
By reversing this query I am getting the teacher's list who take minimum numbers of courses which is 1 but I need to find the list of teacher who don't take any courses.
Left Join and Where condition can help you.
SELECT *
FROM teachers t
LEFT
JOIN courses c
ON t.id = c.teacher_id
WHERE c.teacher_id IS NULL
Teachers don't usually "take" courses. They "teach" courses. Students "take" courses.
That said, not exists seems appropriate:
SELECT t.*
FROM teachers t
WHERE NOT EXISTS (SELECT 1
FROM courses c
WHERE c.teacher_id = t.id
);
If you think about this, it is almost a direct translation of your question: Get teachers where there is no course that the teacher teachers.
Related
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
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);
Hi I am looking to find out what join I would use if I wanted to join 2 tables together. I currently have a list of all students so 25 students to 1 class and the other table only shows 7 of those names with their test results.
What I would like is to have 1:1 join for the ones with the test results and the other ones without I would like to show them underneath so all in all I have 20 records.
If somebody could please advise on how I could achieve this please.
Thanks in advance.
It sounds like you want an OUTER JOIN.
For this example, we'll assume that there is a table named student and that it contains a column named id which is UNIQUE (or PRIMARY) KEY.
We'll also assume that there is another table named test_result which contains a column named student_id, and that column is a foreign key referencing the id column in student.
For demonstration purposes, we'll just make up some names for the other columns that might appear in these tables, name and score.
SELECT s.id
, s.name
, r.score
FROM student s
LEFT
JOIN test_result r
ON r.student_id = s.id
ORDER
BY r.student_id IS NULL
, s.score DESC
, s.id
Note that if student_id is not unique in test_result, there is potential to return multiple rows that match a row in student.
To get (at most) one row returned from test_result per student, we could use an inline view.
SELECT s.id
, s.name
, r.score
FROM student s
LEFT
JOIN ( SELECT t.student_id
, MAX(t.score) AS score
FROM test_result t
GROUP BY t.student_id
) r
ON r.student_id = s.id
ORDER
BY r.student_id IS NULL
, s.score DESC
, s.id
The expressions in the ORDER BY clause are designed to return the students that have matching row(s) in test_result first, followed by students that don't.
This is just a demonstration, and very likely excludes some important criteria, such as which test a score should be returned for. But without a sample schema and some example data, we're just guessing.
You are looking for a left outer join or a full outer join.
The left outer join will show all students and their tests if they have them.
select *
from Students as s
left outer join Tests as t
on s.StudentId = t.StudentId
The full outer join will show all students with their tests if they have them, and tests even if they do not have students.
select *
from Students as s
full outer join Tests as t
on s.StudentId = t.StudentId
Suppose I have the following tables
Students (StudentId int Pk, StudentName nvarchar)
Lectures (LectureId Pk, StartDate, EndDate)
Enrollment (StudentID, LectureID)
When I execute the following query:
select StudentID From Students
I get 8 rows..
And when i execute:
select S.StudentID From Students S join Enrollment En On S.StudentID = En.StudentID
I get 11 Rows
Why is that, and how to use join without retrieving extra rows?
Your join is fine.
This means that there is more than one Enrollment per Student - e.g. Students have enrolled in more than one lecture / series of lectures
Assuming that a student can only register for one lecture at a time, then you will also need to join to lecture and use the date fields
Also, if a student is not currently enrolled in anything, you will need to consider a left outer join.
So your query might then look like
SELECT S.StudentID FROM Students S
LEFT OUTER JOIN Enrollment En On S.StudentID = En.StudentID
INNER JOIN Lectures l ON en.LectureId = l.Lecture ID
WHERE getdate() BETWEEN l.StartDate and l.EndDate
But you would need to have rules in place to ensure that the student cannot concurrently register for more than one lecture (if that is indeed what you expected)
HTH
Use a LEFT JOIN
EDIT: nevermind, it is as said below/above/whatever. You must have students with multiple lectures/enrollments. You will need to group by student to get 8 rows again.
select S.StudentID From Students S
join Enrollment En On S.StudentID = En.StudentID
group by S.StudentID
I have three tables, with these fields:
classes: class_id | name | grade
classes_students: class_id | student_id
students: student_id | name
Classes has a 1:n relationship with Students, so one class can have many students. I want to select all students of a particular class, where class_id is 5.
Could I just do something like this?
SELECT student.name FROM students student
LEFT JOIN classes_students link
ON link.class_id = 5
AND link.student_id = student.student_id
I'm not sure about if I need the join here and if that has to be a "LEFT JOIN"? What I want is only a record set containing the student names.
Use:
SELECT s.name
FROM STUDENTS s
JOIN CLASSES_STUDENTS cs ON cs.student_id = s.student_id
AND cs.class_id = 5
Alternately:
SELECT s.name
FROM STUDENTS s
JOIN CLASSES_STUDENTS cs ON cs.student_id = s.student_id
JOIN CLASSES c ON c.class_id = cs.class_id
WHERE c.class_id = 5
Because you want students only in a particular class, you'd use an INNER JOIN. You'd use a LEFT JOIN if you wanted say a list of all the students, and then LEFT JOIN to CLASSES_STUDENTS to know if any are taking classes (because those that aren't would have null values in the CLASSES_STUDENTS columns). I recommend looking at this link for the breakdown of various JOINs and their impact on the data you'll get back.
SELECT
student.name
FROM
students student
INNER JOIN
classes_students link
ON
link.student_id = student.student_id
WHERE
link.class_id = 5
Your query should actually work fine if you just change your join to INNER, but i much prefer using a where clause to filter the results rather than including it in the JOIN criteria, i think it's clearer.