JOIN where row may not exist in one table - sql

I have two tables:
students
+-------+------+
| id | name |
+-------+------+
| 1 | Bob |
+-------+------+
| 2 | Sam |
+-------+------+
and
courses
+----+------------+---------+--------+
| id | student_id | teacher | period |
+----+------------+---------+--------+
| 1 | 1 | Mr. X | 1 |
+----+------------+---------+--------+
| 2 | 1 | Ms. Y | 2 |
+----+------------+---------+--------+
| 3 | 2 | Mr. X | 2 |
+----+------------+---------+--------+
| 4 | 2 | Ms. Y | 3 |
+----+------------+---------+--------+
And this is the result I need from these two tables:
list of students and period 1 teacher
+------------+------+-----------------+
| student_id | name | period 1 teacher|
+------------+------+-----------------+
| 1 | Bob | Mr. X |
+------------+------+-----------------+
| 2 | Sam | null |
+------------+------+-----------------+
Okay, I need a list of students and the teacher they have for a certain period (in this case, period 1). They may, however, have no teacher listed for that period in the courses table, in which case I want 'null' for that column on that student (as above with 'Sam').
The closest I have is this:
SELECT students.id,students.name,courses.teacher
FROM students
LEFT JOIN courses ON students.id = courses.student_id AND courses.period = '1'
But I only ever get back rows that exist in BOTH tables (in this example, only the 'Bob' student would be returned since 'Sam' has no period 1 teacher.
I feel certain it is something simple, but my Google-fu has failed me thus far.

Can you try this:
SELECT students.id,students.name,courses.teacher
FROM students
LEFT JOIN (select * from courses WHERE courses.period = '1') courses
ON students.id = courses.student_id

if I understand correctly and according to the context is to appear only the student BOB.
Assuming it to be true, replace the LEFT for INNER what will work.
SELECT students.id,students.name,courses.teacher
FROM students
INNER JOIN courses ON students.id = courses.student_id AND courses.period = '1'
I hope it is useful

Related

SQL: How to find rows in one table that have no references to rows in another tables?

I have three tables: users, rooms, room_users.
Users can have many rooms and rooms as well can have many users, so this is many to many relationship.
users table:
+----+-----------+-----+
| id | name | age |
+----+-----------+-----+
| 1 | Christian | 19 |
| 2 | Ben | 36 |
| 3 | Robert | 52 |
| 4 | Monica | 25 |
| 5 | Alice | 26 |
| 6 | William | 18 |
+----+-----------+-----+
rooms table:
+----+----------+
| id | name |
+----+----------+
| 1 | College |
| 2 | Work |
| 3 | Football |
+----+----------+
And room_users table that represents relationship between users and rooms:
+---------+---------+
| user_id | room_id |
+---------+---------+
| 1 | 1 |
| 1 | 3 |
| 2 | 2 |
| 4 | 1 |
| 5 | 2 |
| 6 | 1 |
| 6 | 3 |
+---------+---------+
So, having these tables we can say that:
Christian(1) belongs to College(1) and Football(3) rooms.
Ben(2) belongs to Work(2) room.
Robert(3) does not belong to any room.
Monica(4) belongs to College(1) room.
Alice(5) belongs to Work(2) room.
William(1) belongs to College(1) and Football(3) rooms.
And now if I want to find users (ids) that does belong to Football room I should use this query:
SELECT user_id FROM room_users WHERE room_id = 3
Output for this query:
+---------+
| user_id |
+---------+
| 1 |
| 6 |
+---------+
This is correct, only Christian(1) and William(3) belong to Football room.
But how to find users that does NOT belong to Football room?
In this case, query must return 2, 3, 4 and 5 ids. That is, all IDs excluding IDs from the first query.
Is it possible to do it using LEFT JOIN?
As far as I know, it is more efficient way than using sub-queries.
Thanks in advance!
EDIT:
I've found a query that can solve the problem, but this query is VERY SLOW on large database:
SELECT users.id FROM users WHERE 0=(SELECT COUNT(*) FROM room_users WHERE user_id=users.id AND room_id=3);
Without correlated behavior, try something like this:
SELECT u.*
FROM users AS u
LEFT JOIN (
SELECT DISTINCT user_id FROM room_users WHERE room_id = 3
) AS v
ON v.user_id = u.id
WHERE v.user_id IS NULL
;
For performance issues, start by reviewing the explain/execution plan and use of indexes.
You could find those users that belong to the football room AND then exclude those using not in.
Also you can use a JOIN
SELECT u.*
FROM
users u
WHERE user_id NOT IN
(SELECT user_id FROM room_users WHERE room_id=3)
You are correct that this is possible to do with a left join.
SELECT
u.id
FROM
users u
LEFT JOIN room_users ur
ON u.id = ur.user_id
AND ur.room_id = 3
WHERE
ur.room_id is null;

Add default for missing rows (combinations of students and subjects)

I have a table which holds grades of students in some subjects.
STUDENT_GRADES
id | studentid | subjectid | grade
-----------------------------------
1 | 1 | 1 | A+
2 | 1 | 2 | A
3 | 1 | 3 | A-
4 | 2 | 2 | A
5 | 2 | 3 | A-
6 | 3 | 2 | B
7 | 4 | 3 | B+
and I have another table which holds subject list.
SUBJECTS
subjectid | subjectname
-----------------------------
1 | Maths
2 | Science
3 | History
Now I have to retrieve result in the following format.
FORMATTED_GRADES
studentid | subjectid | grade
-----------------------------------
1 | 1 | A+
1 | 2 | A
1 | 3 | A-
2 | 1 | fail
2 | 2 | A
2 | 3 | A-
3 | 1 | fail
3 | 2 | B
3 | 3 | fail
4 | 1 | fail
4 | 2 | fail
4 | 3 | B+
The FORMATTED_GRADES contains each student from STUDENT_GRADES and his/her grade in each subject. If there is no grade of a student in a subject in STUDENT_GRADES, then the grade of the student in that subject should be fail.
You most probably have another table students with studentid as primary key. Use that in a CROSS JOIN to form a Cartesian product with one row for each combination of student and subject:
SELECT student_id, subject_id, COALESCE(g.grade, 'fail') AS grade
FROM students t
CROSS JOIN subjects u
LEFT JOIN student_grades g USING (student_id, subject_id);
Most importantly, this includes students that failed in all subjects. If there is no entry in student_grades at all, the attempt to distill unique students from student_grades (like #Gordon demonstrates) will miss out on those.
Assuming that subjects has no column named student_id and students has no column subject_id. Else you must use the more explicit join syntax with ON instead of the shortcut with USING.
When joining with the USING clause, only one instance of the (necessarily identical) join columns is in the output table, hence we do not need table qualification in the SELECT list (even if that would be allowed anyway).
Also assuming that student_grades.grade is a character data type. Else you need to cast explicitly to get compatible data types in COALESCE. Like: COALESCE(g.grade::text, 'fail')
Use cross join to generate the rows, and left join and coalesce() to get the data you want:
select st.student_id, su.subject_id,
coalesce(sg.grade, 'fail') as grade
from (select distinct student_id from student_grades) st cross join
subjects su left join
student_grades sg
on sg.student_id = st.student_id and sg.subject_id = su.subject_id

Problems with joining two tables

I'm currently making an application using C# and I am having some difficulty with joining two tables. To make things more clear here are my table structures
Table 1(List of Employee)
| EmployeeID | EmployeeName |
+------------+--------------+
| 1 | John Smith |
| 2 | Ian Smosh |
Table 2(List of Referrals)
| PersonalID | InviterID | InterviewerID |
+------------+-----------+---------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
The output on Datagridview should be
| Employee Name | Invites | Interviews |
+---------------+---------+------------+
| John Smith | 2 | 1 |
| Ian Smosh | 0 | 1 |
I am currently able to get the invites but not the interviews at the same time. I am only able to get one.
Here is what I get
| Employee Name | Invites |
+---------------+---------+
| John Smith | 2 |
| Ian Smosh | 0 |
Here is my code:
SELECT Table1.RecruiterName AS Name,
COUNT(Table2.InviterID) AS Invites,
COUNT(Table2.InterviewID) AS Interviews
FROM Table2 LEFT JOIN Table1 ON Table2.InviterID = Table1.EmployeeID
AND Table2.InterviewerID = Table1.InviterID
GROUP BY EmployeeName
Anyone there knows what's wrong with my code?
UPDATE: I managed to make it a little better but I keep getting
| Employee Name | Invites | Interviews |
+---------------+---------+------------+
| John Smith | 2 | 2 |
| Ian Smosh | 0 | 1 |
The entry for John Smith only has 2 Invites and 1 Interview. This is my current code
SELECT Recruiters.RecruiterName AS Name, COUNT(Source.SourceID) AS Source, COUNT(Interview.InterviewID) AS Interview
FROM Recruiters
LEFT JOIN Hires Source ON Source.SourceID=Recruiters.RecruiterID
LEFT JOIN Hires Interview ON Interview.InterviewID=Recruiters.RecruiterID
GROUP BY RecruiterName
Why is it that John Smith gets a wrong amount in interviews but Ian Smosh is correct.
the double join is double dipping
this should work
select employee.EmployeeName, inv.count, int.count
from employee
join ( select InviterID,
count(*) as count
from referral
group by InviterID ) as inv
on employee.employeeID = inv.InviterID
join ( select InterviewerID,
count(*) as count
from referral
group by InterviewerID ) as int
on employee.employeeID = int.InterviewerID
SELECT Recruiters.RecruiterName AS Name,
(select COUNT(*) from Hires where SourceID = Recruiters.RecruiterID) AS Source,
(select COUNT(*) from Hires where InterviewID = Recruiters.RecruiterID) AS Interview
FROM Recruiters

Join 2 tables and Count the number of occurrence specific field in SQL

I have 2 tables , "T_COMMON_COUNTRY" and "T_HEI_STUDENTDATA." using left join I joined these tables
this is my query
SELECT
[T_COMMON_COUNTRY].[COUNTRY_ID],
[T_COMMON_COUNTRY].[COUNTRY],
[T_HEI_STUDENTDATA].[STUDENT_ID]
FROM ([T_COMMON_COUNTRY]
LEFT JOIN [T_HEI_STUDENTDATA]
ON [T_COMMON_COUNTRY].[COUNTRY] = [T_HEI_STUDENTDATA].[STDCOUNTRY])
now I' getting view like this
| Country ID | County | Student ID |
| 1 | USA | 12 |
| 1 | USA | 5 |
| 2 | UK | 11 |
| 2 | UK | 2 |
I want Count the number of students (Student_IDs) relate to a country ,
I want get a view exactly like below
| Country ID | County | Students |
| 1 | USA | 2 |
| 2 | UK | 2 |
Use COUNT function to generate countrywise student count
Try this:
SELECT C.[COUNTRY_ID], C.[COUNTRY], COUNT(S.[STUDENT_ID]) AS StudentCount
FROM [T_COMMON_COUNTRY] C
LEFT JOIN [T_HEI_STUDENTDATA] S ON C.[COUNTRY] = S.[STDCOUNTRY]
GROUP BY C.[COUNTRY_ID], C.[COUNTRY];
Use COUNT function to count the number of students GROUP BY Country_ID and Country.
SELECT
[T_COMMON_COUNTRY].[COUNTRY_ID],
[T_COMMON_COUNTRY].[COUNTRY],
COUNT([T_HEI_STUDENTDATA].[STUDENT_ID]) AS Students
FROM ([T_COMMON_COUNTRY]
LEFT JOIN [T_HEI_STUDENTDATA]
ON [T_COMMON_COUNTRY].[COUNTRY] = [T_HEI_STUDENTDATA].[STDCOUNTRY])
GROUP BY [T_COMMON_COUNTRY].[COUNTRY_ID], [T_COMMON_COUNTRY].[COUNTRY]

How to GROUP BY into separate columns

I have 3 tables:
The first one contains information about persons. The relevant column is the personID.
The second contains exercises a person can do. There are for example 3 exercises with an exID.
The third contains the points a person (personID) reached in an exercise (exID). So each row here stands for an examination a person has taken. But not everyone need to have taken every exam.
What I would like to have is a result with the columns personID, exam_one, exam_two, exam_three, ...(ongoing, depending on how many exams there are). And each row of the result should contain the personID and the points from the respective exam.
For exams not taken there should be NULL or something.
Example for table persons:
personID | Name | ...
-------------------
1 | Max |
2 | Peter |
Example for exercises table:
exID | exName | maxPoints | ...
-------------------------------
1 | exam1 | 20
2 | exam2 | 25
3 | exam3 | 20
Example for points table:
personID (fkey) | exID (fkey) | points
----------------------------------------
1 | 1 | 12.5
1 | 3 | 10
2 | 1 | 5
2 | 2 | 8.5
2 | 3 | 10
Wished result:
personId | exam1 | exam2 | exam3
------------------------------------
1 | 12.5 | NULL | 10
2 | 5 | 8.5 | 10
Is there a way to do this? I use PostgreSQL
You can use something like the following:
select p.personId,
sum(case when e.exname = 'exam1' then t.points end) Exam1,
sum(case when e.exname = 'exam2' then t.points end) Exam2,
sum(case when e.exname = 'exam3' then t.points end) Exam3
from persons p
left join points t
on p.personID = t.personID
left join exercises e
on t.exid = e.exid
group by p.personid
See SQL Fiddle with Demo