Also...
WHERE EXISTS(...) bedeutet im Prinzip WHERE COUNT_RECORDS(...) > 0
Dabei ist COUNT_RECORDS() eine gedachte Funktion, die einfach guckt wie viele Zeilen ein SELECT ausspuckt.
Damit das ganze nun sinnvoll anwendbar ist, muss man es natürlich mit den Daten der Zeile kombinieren können, die gerade mit WHERE gefiltert wird (die "aktuelle" Zeile des umgebenden SELECTs).
Das passiert nun einfach dadurch, dass alle Bezeichner die nicht "lokal" aufgelöst werden können im "umgebenden" SELECT gesucht und ausgewertet werden.
Angenommen du willst Veranstaltungen auflisten die ein bestimmter Student, dessen ID (Matrikelnummer) du bereits hast, besucht.
Dann kannst du schreiben
SELECT * FROM Veranstaltung
WHERE EXISTS(
SELECT * FROM VListe
WHERE MATNR = 1234
AND VERAN = Veranstaltung.Code
)
Veranstaltung.Code ist hier der Bezeichner der nicht "lokal" aufgelöst werden kann, er wird also im äusseren SELECT gesucht. Dort gibt es ihn: der "Code" der Veranstaltung der Zeile die gerade gefiltert wird.
Bzw. wenn du wissen willst welche Veranstaltungen dieser Student besucht, die genau 2 Semesterwochenstunden haben, dann kannst du schreiben:
SELECT * FROM Veranstaltung
WHERE SWS = 2
AND EXISTS(
SELECT * FROM VListe
WHERE VERAN = Veranstaltung.CODE
AND MATNR = 1234
)
Klar soweit?
Das dabei entstandene SELECT ist aber ein ganz normales SELECT, und kann natürlich seinerseits wieder in EXISTS verwendet werden.
Jetzt wollen wir wissen welche Veranstaltungen, die genau 2 SWS haben, der Student NICHT besucht. Das ist einfach, wir stellen ein NOT vor das EXISTS:
SELECT * FROM Veranstaltung
WHERE SWS = 2
AND >>NOT<< EXISTS(
SELECT * FROM VListe
WHERE VERAN = Veranstaltung.CODE
AND MATNR = 1234
)
Und wenn wir jetzt wissen wollen, für welche Studenten es Veranstaltungen gibt, die genau 2 SWS haben, aber nicht vom Student besucht werden, dann müssen wir einfach für alle Studenten gucken ob die Abfrage die wir gerade gebastelt haben keine Zeilen ergibt.
D.h.
SELECT MATNR, PID FROM Student
WHERE EXISTS(
"Veranstaltungen die dieser Student nicht besucht, und die genau 2 SWS haben"
)
Jetzt musst du nur noch einsetzen. Und statt der fixen MATNR in der Abfrage den "äusseren Bezeichner" hinschreiben, der für die MATNR der Zeile des äussersten SELECTs steht. Und das ist dann:
SELECT MATNR, PID FROM Student
WHERE EXISTS(
SELECT * FROM Veranstaltung
WHERE SWS = 2
AND NOT EXISTS(
SELECT * FROM VListe
WHERE VERAN = Veranstaltung.CODE
AND MATNR = >>Student.MATNR<<
)
)
Und genau das ist die Lösung.
Achja, falls das der Knackpunkt für dich sein sollte: beim Bezeichner (Namen) auflösen geht es von innen nach aussen, Schritt für Schritt.
Also "VERAN" z.B. wird schon im innersten SELECT gefunden, also nimmt die Abfrage es von dort.
"Veranstaltung.CODE" gibt es im innersten SELECT nicht, dafür aber im "mittleren" (eins weiter aussen), also wird es von dort genommen.
Und "Student.MATNR" gibt es weder im innersten noch im mittleren SELECT, also wird noch eins weiter aussen gesucht - im äusseren SELECT.
Gäbe es dort auch keinen solchen Bezeichner, würde die SQL Abfrage entsprechend nicht compilieren.