Post on 14-Oct-2020
RELATIONAL ALGEBRA FOR BEGINNERS
Annamari Soini, dept. of Information Technology Åbo Akademi
Course database:
Course_scheme = (code, coursename, cu, period)Student_scheme = (studentnumber, name, UP, sex) Staff_scheme = (name, title)Who_does_what_scheme = (teacher, code)1
Course_registration_scheme = (studentnumber, code)
course course_registration
code coursename cu period
studentnumber code
A001 History of Art 8 1 SA01 A001
A002 Renaissance Art 5 2 SA01 A002
A003 Modern Art 5 3 SA02 A001
E001 English Grammar 5 1 SA02 A002
E002 English Literature 5 2 SA02 A003
E003 Shakespeare 1 5 3 SA03 A003
L001 Latin 1 5 1 SE01 E001
L002 Latin 2 5 2 SE01 E002
L003 Caesar 5 3 SE01 A001
L004 Catullus 5 4 SE02 E003
CS001 Introduction to CS 5 1 SE03 A001
CS002 Programming 1 5 1 SE03 L003
CS003 Programming 2 5 2 SL01 L003
CS004 Databases 5 3 SL02 L004
CS005 Data Structures 5 4 SL03 L004
SL03 A001
SCS01 CS004
SCS02 CS004
SCS03 CS005
SCS04 CS005
SCS05 A003
SCS05 L004
1 Allows several teachers per course, and several courses per teacher.
student staff
studentnumber
name UP sex name title
SA01 Mirella Conti Arts F Caravaggio Dr.
SA02 Fleur D'Amour Arts F Picasso M. A.
SA03 Belle Visiteur Arts F Austen Dr.
SA04 Carlo Straniero Arts M Eliot Dr.
SE01 Will Smith English M Nero M. A.
SE02 Angela Bowie English F Turing Dr.
SE03 Sharon Manners English F Church M. SC.
SE04 Jack Harper English M
SL01 Lucius Valerius Latin M who_does_what
SL02 Antonius Primus Latin M teacher code
SL03 Fulvia Morbida Latin F Caravaggio A001
SL04 Septimus Romanus Latin M Caravaggio A002
SCS01 Betty Buffer CS F Picasso A003
SCS02 Bill Bitwise CS M Austen E001
SCS03 Lily Float CS F Austen E002
SCS04 Judy Python CS F Eliot E003
SCS05 Percy Pascal CS M Nero L001
Nero L002
Nero L003
Nero L004
Church CS001
Church CS002
Church CS003
Turing CS004
Turing CS005
chooses columns (attributes)
coursename(course):
coursename
History of Art
Renaissance Art
Modern Art
English Grammar
English Literature
Shakespeare 1
Latin 1
Latin 2
Caesar
Catullus
Introduction to CS
Programming 1
Programming 2
Databases
Data Structures
chooses rows (tuples)
period = 3(course):
code coursename cu period
A003 Modern Art 5 3
E003 Shakespeare 1 5 3
L003 Caesar 5 3
CS004 Databases 5 3
temp period = 4(course)
temp:
code coursename cu period
L004 Catullus 5 4
CS005 Data Structures 5 4
What are the courses given by Turing?
• First pick the table that has that information (who_does_what).
• Then select () the rows where teacher = 'Turing'.
• Finally project () the column(s) asked for (code).
who_does_what
teacher code
Caravaggio A001
Caravaggio A002
Picasso A003
Austen E001
Austen E002
Eliot E003
Nero L001
Nero L002
Nero L003
Nero L004
Church CS001
Church CS002
Church CS003
Turing CS004
Turing CS005
teacher = 'Turing' (who_doeswhat)
teacher code
Turing CS004
Turing CS005
code (teacher = 'Turing' (who_doeswhat))
code
CS004
CS005
What are these courses called?
temp code (teacher = 'Turing' (who_doeswhat))
tempcode
CS004
CS005
• Which table has the information required (the names of the courses)? • combine course and temp:
temp course (every row of temp paired with every row of course):
(2 x 15 rows, and most of these will be nonsense)
temp course
temp.code course.code coursename cu period
CS004 A001 History of Art 8 1
CS005 A001 History of Art 8 1
CS004 A002 Renaissance Art 5 2
CS005 A002 Renaissance Art 5 2
CS004 A003 Modern Art 5 3
CS005 A003 Modern Art 5 3
CS004 E001 English Grammar 5 1
CS005 E001 English Grammar 5 1
CS004 E002 English Literature 5 2
CS005 E002 English Literature 5 2
... ...
CS004 CS004 Databases 5 3
CS005 CS004 Databases 5 3
CS004 CS005 Data Structures 5 4
CS005 CS005 Data Structures 5 4
Select only the sensible rows (where temp.code = course.code):
temp.code = course.code ( temp course)
Parenthesis () tell you which table(s) named or anonymous you go to for information.
temp.code = course.code ( temp course):
temp course
temp.code course.code coursename cu period
CS004 A001 History of Art 8 1
CS005 A001 History of Art 8 1
CS004 A002 Renaissance Art 5 2
CS005 A002 Renaissance Art 5 2
CS004 A003 Modern Art 5 3
CS005 A003 Modern Art 5 3
CS004 E001 English Grammar 5 1
CS005 E001 English Grammar 5 1
CS004 E002 English Literature 5 2
CS005 E002 English Literature 5 2
... ...
CS004 CS004 Databases 5 3
CS005 CS004 Databases 5 3
CS004 CS005 Data Structures 5 4
CS005 CS005 Data Structures 5 4
... giving you
temp.code course.code coursename cu period
CS004 CS004 Databases 5 3
CS005 CS005 Data Structures 5 4
Now you can write the expression:
coursename (temp.code = course.code ( temp course))
coursename (temp.code = course.code ( temp course)):
coursename
Databases
Data Structures
You can replace the cartesian product (cross product) temp course with a natural join (1): this will automatically choose only the “sensible” rows
(combinations) from the cross product.
This is how the natural join (1) works:
• Use the common attribute (or attributes, if several), in this case code.• Only the rows with the same value for this attribute get chosen:
temp 1 course:
temp.code course.code coursename cu period
CS004 CS004 Databases 5 3
CS005 CS005 Data Structures 5 4
• Finally duplicate attributes are removed:
code coursename cu period
CS004 Databases 5 3
CS005 Data Structures 5 4
Now we can write our expression as follows:
coursename (temp 1 course):
coursename (temp 1 course):
coursename
Databases
Data Structures
A couple more exercises on and 1 :
• Find the names of the students who take A001
name (student.student_number = course_registration.studentnumber
∧ code = 'A001' (student course_registration))
The cross product student course_registration will have 17 x 22 rows;every row from student (17 rows) will be combined with every row from course_registration (22 rows), giving you a table with 374 rows!
studentnumber
name UP sex studentnumber
code
SA01 Mirella Conti Arts F SA01 A001
SA01 Mirella Conti Arts F SA01 A002
SA01 Mirella Conti Arts F SA02 A001
SA01 Mirella Conti Arts F SA02 A002
SA01 Mirella Conti Arts F SA02 A003
SA01 Mirella Conti Arts F SA03 A003
SA01 Mirella Conti Arts F SE01 E001
SA01 Mirella Conti Arts F SE01 E002
SA01 Mirella Conti Arts F SE01 A001
SA01 Mirella Conti Arts F SE02 E003
SA01 Mirella Conti Arts F SE03 A001
SA01 Mirella Conti Arts F SE03 L003
SA01 Mirella Conti Arts F SL01 L003
SA01 Mirella Conti Arts F SL02 L004
SA01 Mirella Conti Arts F SL03 L004
SA01 Mirella Conti Arts F SL03 A001
SA01 Mirella Conti Arts F SCS01 CS004
SA01 Mirella Conti Arts F SCS02 CS004
SA01 Mirella Conti Arts F SCS03 CS005
SA01 Mirella Conti Arts F SCS04 CS005
SA01 Mirella Conti Arts F SCS05 A003
SA01 Mirella Conti Arts F SCS05 L004
SA02 Fleur D'Amour Arts F SA01 A001
SA02 Fleur D'Amour Arts F SA01 A002
... ... ... ... ... ...
SCS05 Percy Pascal CS M SCS05 L004
The result will be:
name
Mirella Conti
Fleur D'Amour
Will Smith
Sharon Manners
Fulvia Morbida
... or you can use natural join:
name (code = 'A001' ( student 1 course_registration))
student 1 course_registration:
• I studentnumber is shown here twice, but the natural join eliminates one of these identical columns (see next page!).
studentnumber
name UP sex studentnumber
code
SA01 Mirella Conti Arts F SA01 A001
SA01 Mirella Conti Arts F SA01 A002
SA02 Fleur D'Amour Arts F SA02 A001
SA02 Fleur D'Amour Arts F SA02 A002
SA02 Fleur D'Amour Arts F SA02 A003
SA03 Belle Visiteur Arts F SA03 A003
SE01 Will Smith English M SE01 E001
SE01 Will Smith English M SE01 E002
SE01 Will Smith English M SE01 A001
SE02 Angela Bowie English F SE02 E003
SE03 Sharon Manners English F SE03 A001
SE03 Sharon Manners English F SE03 L003
SL01 Lucius Valerius Latin M SL01 L003
SL02 Antonius Primus Latin M SL02 L004
SL03 Fulvia Morbida Latin F SL03 L004
SL03 Fulvia Morbida Latin F SL03 A001
SCS01 Betty Buffer CS F SCS01 CS004
SCS02 Bill Bitwise CS M SCS02 CS004
SCS03 Lily Float CS F SCS03 CS005
SCS04 Judy Python CS F SCS04 CS005
SCS05 Percy Pascal CS M SCS05 A003
SCS05 Percy Pascal CS M SCS05 L004
student 1 course_registration:
• Duplicate column studentnumber eliminated:
studentnumber
name UP sex code
SA01 Mirella Conti Arts F A001
SA01 Mirella Conti Arts F A002
SA02 Fleur D'Amour Arts F A001
SA02 Fleur D'Amour Arts F A002
SA02 Fleur D'Amour Arts F A003
SA03 Belle Visiteur Arts F A003
SE01 Will Smith English M E001
SE01 Will Smith English M E002
SE01 Will Smith English M A001
SE02 Angela Bowie English F E003
SE03 Sharon Manners English F A001
SE03 Sharon Manners English F L003
SL01 Lucius Valerius Latin M L003
SL02 Antonius Primus Latin M L004
SL03 Fulvia Morbida Latin F L004
SL03 Fulvia Morbida Latin F A001
SCS01 Betty Buffer CS F CS004
SCS02 Bill Bitwise CS M CS004
SCS03 Lily Float CS F CS005
SCS04 Judy Python CS F CS005
SCS05 Percy Pascal CS M A003
SCS05 Percy Pascal CS M L004
Now choose only those rows where code equals A001:
code = 'A001' ( student 1 course_registration):
studentnumber
name UP sex code
SA01 Mirella Conti Arts F A001
SA01 Mirella Conti Arts F A002
SA02 Fleur D'Amour Arts F A001
SA02 Fleur D'Amour Arts F A002
SA02 Fleur D'Amour Arts F A003
SA03 Belle Visiteur Arts F A003
SE01 Will Smith English M E001
SE01 Will Smith English M E002
SE01 Will Smith English M A001
SE02 Angela Bowie English F E003
SE03 Sharon Manners English F A001
SE03 Sharon Manners English F L003
SL01 Lucius Valerius Latin M L003
SL02 Antonius Primus Latin M L004
SL03 Fulvia Morbida Latin F L004
SL03 Fulvia Morbida Latin F A001
SCS01 Betty Buffer CS F CS004
SCS02 Bill Bitwise CS M CS004
SCS03 Lily Float CS F CS005
SCS04 Judy Python CS F CS005
SCS05 Percy Pascal CS M A003
SCS05 Percy Pascal CS M L004
... gives us the following table:
code = 'A001' ( student 1 course_registration):
studentnumber
name UP sex studentnumber
code
SA01 Mirella Conti Arts F SA01 A001
SA02 Fleur D'Amour Arts F SA02 A001
SE01 Will Smith English M SE01 A001
SE03 Sharon Manners English F SE03 A001
SL03 Fulvia Morbida Latin F SL03 A001
name (code = 'A001' ( student 1 course_registration))
gives you now the same result:
name
Mirella Conti
Fleur D'Amour
Will Smith
Sharon Manners
Fulvia Morbida
Three more joins (ways to combine two tables):
• left outer join • right outer join • full outer join
• left outer join : student courseregistration:• First you perform natural join, then you fill in information from the left
side table (those rows that did not get paired with the natural join), in this case those students who are not registered on any course.
studentnumber
name UP sex code
SA01 Mirella Conti Arts F A001
SA01 Mirella Conti Arts F A002
SA02 Fleur D'Amour Arts F A001
SA02 Fleur D'Amour Arts F A002
SA02 Fleur D'Amour Arts F A003
SA03 Belle Visiteur Arts F A003
SE01 Will Smith English M E001
SE01 Will Smith English M E002
SE01 Will Smith English M A001
SE02 Angela Bowie English F E003
SE03 Sharon Manners English F A001
SE03 Sharon Manners English F L003
SL01 Lucius Valerius Latin M L003
SL02 Antonius Primus Latin M L004
SL03 Fulvia Morbida Latin F L004
SL03 Fulvia Morbida Latin F A001
SCS01 Betty Buffer CS F CS004
SCS02 Bill Bitwise CS M CS004
SCS03 Lily Float CS F CS005
SCS04 Judy Python CS F CS005
SCS05 Percy Pascal CS M A003
SCS05 Percy Pascal CS M L004
SA04 Carlo Straniero Arts M NULL
SE04 Jack Harper English M NULL
SL04 Septimus Romanus Latin M NUL
• right outer join : courseregistration course:• First natural join, then fill in information from the right side table! (In this
case, courses that have no registered students.)studentnumber
code coursename cu period
SA01 A001 History of Art 8 1
SA01 A002 Renaissance Art 5 2
SA02 A001 History of Art 8 1
SA02 A002 Renaissance Art 5 2
SA02 A003 Modern Art 5 3
SA03 A003 Modern Art 5 3
SE01 E001 English Grammar 5 1
SE01 E002 English Literature 5 2
SE01 A001 History of Art 8 1
SE02 E003 Shakespeare 1 5 3
SE03 A001 History of Art 8 1
SE03 L003 Caesar 5 3
SL01 L003 Caesar 5 3
SL02 L004 Catullus 5 4
SL03 L004 Catullus 5 4
SL03 A001 History of Art 8 1
SCS01 CS004 Databases 5 3
SCS02 CS004 Databases 5 3
SCS03 CS005 Data Structures 5 4
SCS04 CS005 Data Structures 5 4
SCS05 A003 Modern Art 5 3
SCS05 L004 Catullus 5 4
NULL L001 Latin 1 5 1
NULL L002 Latin 2 5 2
NULL CS001 Introduction to CS 5 1
NULL CS002 Programming 1 5 1
NULL CS003 Programming 2 5 2
full outer join : scholarship student
To exemplify this join, we define a table scholarship, with information of which scholarships there are, and which students have received them (we assume that there may only be one student who gets the scholarship, and some scholarships have not yet been awarded to anyone):
Scholarship_scheme = (scholarshipname, studentnumber)
scholarship
scholarshipname studentnumber
Top Latin SL03
Logic Excellence SCS04
Arts Achievement NULL
Creative Writer SE01
Top Athlete NULL
First make the natural join: scholarship 1 student:
scholarshipname studentnumber
name UP sex
Top Latin SL03 Fulvia Morbida Latin F
Logic Excellence SCS04 Judy Python CS F
Creative Writer SE01 Will Smith English M
Then fill in information from the left side table (scholarship), namely those rows which did not find a pair in the natural join (that is, scholarships that are not awarded to any student as yet):
scholarshipname studentnumber
name UP sex
Top Latin SL03 Fulvia Morbida Latin F
Logix Excellence SCS04 Judy Python CS F
Creative Writer SE01 Will Smith English M
Arts Achievement NULL NULL NULL NULL
Top Athlete NULL NULL NULL NULL
Finally, fill in with information from the right side table (those students who did not get a scholarship):
scholarshipname studentnumber
name UP sex
Top Latin SL03 Fulvia Morbida Latin F
Logix Excellence SCS04 Judy Python CS F
Creative Writer SE01 Will Smith English M
Arts Achievement NULL NULL NULL NULL
Top Athlete NULL NULL NULL NULL
NULL SA01 Mirella Conti Arts F
NULL SA02 Fleur D'Amour Arts F
NULL SA03 Belle Visiteur Arts F
NULL SA04 Carlo Straniero Arts M
NULL SE02 Angela Bowie English F
NULL SE03 Sharon Manners English F
NULL SE04 Jack Harper English M
NULL SL01 Lucius Valerius Latin M
NULL SL02 Antonius Primus Latin M
NULL SL04 Septimus Romanus Latin M
NULL SCS01 Betty Buffer CS F
NULL SCS02 Bill Bitwise CS M
NULL SCS03 Lily Float CS F
NULL SCS05 Percy Pascal CS M
Aggregate functions:
• Work on a collection of rows (“aggregate”) to produce one result(a set with just one value as a member).
• How many courses are given in period 1?
Gcount() ( period = 1 ( course)):
course
code coursename cu period
A001 History of Art 8 1
A002 Renaissance Art 5 2
A003 Modern Art 5 3
E001 English Grammar 5 1
E002 English Literature 5 2
E003 Shakespeare 1 5 3
L001 Latin 1 5 1
L002 Latin 2 5 2
L003 Caesar 5 3
L004 Catullus 5 4
CS001 Introduction to CS 5 1
CS002 Programming 1 5 1
CS003 Programming 2 5 2
CS004 Databases 5 3
CS005 Data Structures 5 4
• How many cu:s does one get if one takes all the courses that are given in period 1?
Gsum(cu) (period = 1 ( course))
5
Gsum(cu) (period = 1 ( course)):
course
code coursename cu period
A001 History of Art 8 1
A002 Renaissance Art 5 2
A003 Modern Art 5 3
E001 English Grammar 5 1
E002 English Literature 5 2
E003 Shakespeare 1 5 3
L001 Latin 1 5 1
L002 Latin 2 5 2
L003 Caesar 5 3
L004 Catullus 5 4
CS001 Introduction to CS 5 1
CS002 Programming 1 5 1
CS003 Programming 2 5 2
CS004 Databases 5 3
CS005 Data Structures 5 4
With aggregate functions one sometimes uses grouping:
• first you form the groups• then the operation requested is performed in each group separately:
28
• “How many courses are given in each period?”
periodGcount()(course)
• First, form the groups, using period as the criterion:
code coursename cu period
A001 History of Art 8 1
E001 English Grammar 5 1
L001 Latin 1 5 1
CS001 Introduction to CS 5 1
CS002 Programming 1 5 1
A002 Renaissance Art 5 2
E002 English Literature 5 2
L002 Latin 2 5 2
CS003 Programming 2 5 2
A003 Modern Art 5 3
E003 Shakespeare 1 5 3
L003 Caesar 5 3
CS004 Databases 5 3
L004 Catullus 5 4
CS005 Data Structures 5 4
• Then, count the rows in each group:
period count()
1 5
2 4
3 4
4 2
• “How many points will each student get if they pass all the courses they are registered on?”
◦ First form the groups: one group for each student, containing the courses this student is registered on, then add the cucolumns:
studentnumberGsum(cu)(course_registration 1 course) ... first:
studentnumber
code coursename cu period
SA01 A001 History of Art 8 1
SA01 A002 Renaissance Art 5 2
SA02 A001 History of Art 8 1
SA02 A002 Renaissance Art 5 2
SA02 A003 Modern Art 5 3
SA03 A003 Modern Art 5 3
SE01 E001 English Grammar 5 1
SE01 E002 English Literature 5 2
SE01 A001 History of Art 8 1
SE02 E003 Shakespeare 1 5 3
SE03 A001 History of Art 8 1
SE03 L003 Caesar 5 3
SL01 L003 Caesar 5 3
SL02 L004 Catullus 5 4
SL03 L004 Catullus 5 4
SL03 A001 History of Art 8 1
SCS01 CS004 Databases 5 3
SCS02 CS004 Databases 5 3
SCS03 CS005 Data Structures 5 4
SCS04 CS005 Data Structures 5 4
SCS05 A003 Modern Art 5 3
SCS05 L004 Catullus 5 4
Then: studentnumberGsum(cu)(course_registration 1 course)
studentnumber
sum(cu)
SA01 13
SA02 18
SA03 5
SE01 18
SE02 5
SE03 13
SL01 5
SL02 5
SL03 13
SCS01 5
SCS02 5
SCS03 5
SCS04 5
SCS05 10
As you can see, the attribute we use for grouping (studentnumber) and the result of the aggregate function (sum(cu)) are projected in the result.
Set operations
• union ∪• intersection ∩• difference • division (you will also get a separate pdf on this!)• multiplication you are already familiar with!
UNION: combines the elements of two sets
• the elements of each set must have the same type• duplicate elements are eliminated from the result
Example: “Which students take either A001 or L004?”
studentnumber(code = 'A001' ( course_registration))
studentnumber(code = 'L004' ( course_registration))
Those who take A001:
studentnumber
SA01
SA02
SE01
SE03
SL03
Those who take L004:
studentnumber
SL02
SL03
SCS05
studentnumber(code = 'A001' ( course_registration))
studentnumber(code = 'L004' ( course_registration))
studentnumber
SA01
SA02
SE01
SE03
SL03
SL02
SCS05
Note that the student SL03 who takes both courses shows in the result only once!
A001
L004
SA01
SA02
SE01
SE03
SL03
SL02
SCS05
INTERSECTION: chooses the elements that are in both sets:
Example: “Which students take both A001 and L004?”
studentnumber(code = 'A001' ( course_registration))
studentnumber(code = 'L004' ( course_registration))
Those who take A001: Those who take L004:
studentnumber studentnumber
SA01 SL02
SA02 SL03
SE01 SCS05
SE03
SL03
The result:
studentnumber
SL03
A001
L004
SA01
SA02
SE01
SE03
SL03
SL02
SCS05
SET DIFFERENCE: Which elements are in the first set but NOT in the other?
Example: “Which students take A001 but not L004?”
studentnumber(code = 'A001' ( course_registration)) -
studentnumber(code = 'L004' ( course_registration))
Those who take A001: Those who take L004:
studentnumber studentnumber
SA01 SL02
SA02 SL03
SE01 SCS05
SE03
SL03
Result:
studentnumber
SA01
SA02
SE01
SE03
This part not this part
A001
L004
SA01
SA02
SE01
SE03
SL03
SL02
SCS05
DIVISION: Which elements share ALL of another relation? I. e., which elements have ALL of this other relation in common.
• Used for queries with “all”:• “Which students take ALL the courses given in period 1?”
◦ All the courses given in period 1 is the set (A001, E001, L001, CS001, CS002)and we ask which students, if any, are registered in ALL of these courses. They have all of these courses in common.
• “Which female students take ALL the courses that student nr. SE01 is registered on?” (CSI might be interested in finding out this, if SE01, Will Smith, gets murdered by a mystery woman ...)
◦ All the courses SE01 is registered on is the set(E001, E002, A001)and we ask which female students, if any, are registered on ALL of these courses. They have all of these courses in common.
• “Which students take ALL the courses taught by Caravaggio?” (We might give this query if we want to find out who are Caravaggio's greatest fans.)
◦ All the courses taught by Caravaggio is the set(A001, A002)and we ask which students, if any, are registered on ALL of these courses. They have all of these courses in common.
• BUT NOT: “Which students have passed (if we had this information) ALL the courses they are registered on?”
◦ In this case there is no COMMON set of courses; the set to be tested varies according to the student in question. There is (most probably) no set of courses they would all have in common.
Example: “Which students take ALL the courses taught by Caravaggio?”
• First, find out which courses are taught by Caravaggio:
Caravaggios_courses code (teacher = 'Caravaggio' (who_doeswhat))
who_does_what
teacher code
Caravaggio A001
Caravaggio A002
Picasso A003
Austen E001
Austen E002
Eliot E003
Nero L001
Nero L002
Nero L003
Nero L004
Church CS001
Church CS002
Church CS003
Turing CS004
Turing CS005
Caravaggios_courses:
code
A001
A002
Now divide the table course_registration by Caravaggios_courses:
course_registration Caravaggios_courses:
code
A001
A002
course_registration:
studentnumber code
SA01 A001
SA01 A002
SA02 A001
SA02 A002
SA02 A003
SA03 A003
SE01 E001
SE01 E002
SE01 A001
SE02 E003
SE03 A001
SE03 L003
SL01 L003
SL02 L004
SL03 L004
SL03 A001
SCS01 CS004
SCS02 CS004
SCS03 CS005
SCS04 CS005
SCS05 A003
SCS05 L004
The only students who have ALL (both) of these courses are numbers SA01 and SA02. They are the result of our query.
course_registration Caravaggios_courses:
studentnumber
SA01
SA02
That SA02 also takes a third course (not by Caravaggio) does not affect the result.
• Observe that the column used for division, code, is eliminated from the result!