You could end up using a monster like the following (fiddle):
select persons.firstName, persons.lastName,
semesters.semesterID, semesters.startDate
from persons, semesters,
(select p.personID,
(select semesters.semesterID
from semesters, classes, class_person
where semesters.semesterID = classes.semesterID
and classes.classID = class_person.classID
and class_person.personID = p.personID
order by semesters.startDate
limit 1) as semesterID
from (select distinct personID from class_person) as p
) as ps
where persons.personID = ps.personID
and semesters.semesterID = ps.semesterID
The subquery p
identifies all persons. For each, ps
will contain a single row. Its personID
is simply copied, its semesterID
is computed by a subquery, which sorts semesters by date but returns the ID. The outermost query then re-adds the date.
If you don't really need the semesterID
, you could avoid one layer. If your semesters are in order, i.e. their IDs have the same order as their startDates, then you could simply use a single query, much like your own, and return min(semesterID)
and min(startDate)
.
On the whole, this question reminds me a lot of my own question, Select one value from a group based on order from other columns. Answers suggested there will likely apply here as well. In particular, there are approaches using user variables which I still don't feel comfortable about, but which will make this whole mess a lot easier and seem to work well enough. So adapting this answer, you get a query like this (fiddle):
SELECT p.firstName, p.lastName, s2.semesterID, s2.startDate
FROM persons p
INNER JOIN (
SELECT @rowNum:=IF(@personID=cp.personID,@rowNum+1,1) rowNum,
@personId:=cp.personID personID,
s.semesterID, s.startDate
FROM (SELECT @personID:=NULL,@rowNum:=0) dummy
INNER JOIN semesters s
INNER JOIN classes c ON s.semesterID = c.semesterID
INNER JOIN class_person cp ON cp.classID = c.classID
ORDER BY cp.personID, s.startDate
) s2 ON p.personID = s2.personID
WHERE s2.rowNum = 1
I'll leave adapting the other answers as an excercise.