I have a typical many-to-many relationship resolved by a join table, as shown here:
For this question the sample data is:
People:
PersonID | Name |
---|---|
P001 | Alice |
P002 | Bob |
P003 | Carlos |
P004 | David |
Courses:
CourseID | Course |
---|---|
C001 | Algebra |
C002 | Biology |
C003 | Chemistry |
Attendance:
CourseID | PersonID |
---|---|
C001 | P001 |
C001 | P002 |
C001 | P003 |
C002 | P002 |
C002 | P003 |
C003 | P003 |
The following query:
SELECT Courses.CourseID, Courses.Course, People.PersonID, People.Name
FROM Courses
INNER JOIN Attendance ON Courses.CourseID = Attendance.CourseID
INNER JOIN People ON Attendance.PersonID = People.PersonID
gives me a flat list of attendances by course:
C001,Algebra,P001,Alice
C001,Algebra,P002,Bob
C001,Algebra,P003,Carlos
C002,Biology,P002,Bob
C002,Biology,P003,Carlos
C003,Chemistry,P003,Carlos
Now I am trying to get a similar, but opposite result - i.e. a list of people who did NOT attend each course, which should look like this:
Expected result:
C001,Algebra,P004,David
C002,Biology,P001,Alice
C002,Biology,P004,David
C003,Chemistry,P001,Alice
C003,Chemistry,P001,Bob
C003,Chemistry,P004,David
I was hoping I could use something like:
SELECT Courses.CourseID, Courses.Course, People.PersonID, People.Name
FROM Courses
CROSS JOIN People
WHERE People.PersonID IN (
SELECT Attendance.PersonID
FROM Courses
INNER JOIN Attendance ON Attendance.CourseID = Courses.CourseID
)
but unfortunately that returns:
C001,Algebra,P001,Alice
C001,Algebra,P002,Bob
C001,Algebra,P003,Carlos
C002,Biology,P001,Alice
C002,Biology,P002,Bob
C002,Biology,P003,Carlos
C003,Chemistry,P001,Alice
C003,Chemistry,P002,Bob
C003,Chemistry,P003,Carlos
which suggests that the subquery is performed once for all courses, not individually for each course.
IMPORTANT:
The application I am using (Claris FileMaker) implements a very limited subset of the SQL-92 standard (and isn't very well documented to boot). I can only use the SELECT statement with a handful of clauses, and AFAICT only INNER JOIN, LEFT OUTER JOIN and CROSS JOIN are allowed. Also I haven't been able to implement a subquery anywhere except inside a WHERE clause.
Is it at all possible to return the expected result using only plain, basic SQL?
something like this should work
select *
from Courses as c cross join People as p
left outer join Attendance as a
on (c.CourseID = a.CourseID and p.PersonID = a.PersonID)
where a.AttendId is null
the main trick is to generate Cartesian product of all courses/people first - ie all combinations
then, we use left join to find matching attendance records, but because it is LEFT join - it will not exclude non matching records, all non matching combinations will have null as value from attendance table