Manchmal reicht es nicht, Daten einfach nur zu gruppieren und das Ergebnis zu filtern – manchmal braucht man noch zusätzliche Logik. Zum Beispiel willst du den Durchschnitt der Noten von Studierenden in einer Gruppe mit irgendeinem externen Kriterium vergleichen. Hier kommt HAVING mit Subqueries ins Spiel – ein mächtiges Tool, das dir erlaubt, smartere Entscheidungen direkt im SQL-Query zu treffen.
Erinnern wir uns an HAVING
Lass uns auf Subqueries schauen, die zusammen mit HAVING verwendet werden, um Daten auf aggregierter Ebene zu filtern. Warum? WHERE filtert einzelne Zeilen, aber HAVING kommt erst nach der Gruppierung ins Spiel – das ist eine andere Analyse-Ebene, die dir mehr Möglichkeiten gibt.
Bevor wir tiefer in die Kombination von Subqueries und HAVING eintauchen, lass uns kurz auffrischen, was HAVING ist und wie es sich von WHERE unterscheidet.
WHEREfiltert Zeilen vor der Gruppierung (GROUP BY).HAVINGfiltert Daten nach der Aggregation, wenn die Daten schon gruppiert sind.
Stell dir vor, du analysierst Studierende und deren Noten. Mit WHERE kannst du Studierende mit bestimmten Mindestnoten ausschließen, aber mit HAVING kannst du ganze Gruppen von Studierenden basierend auf deren Durchschnitts- oder Maximalnote ausschließen.
Beispieldaten
Hier ist eine Tabelle mit Beispiel-Studierenden:
Tabelle students:
| student_id | student_name | department | grade |
|---|---|---|---|
| 1 | Alex | Physics | 80 |
| 2 | Maria | Physics | 85 |
| 3 | Dan | Math | 90 |
| 4 | Lisa | Math | 60 |
| 5 | John | History | 70 |
Beispiel für HAVING (ohne Subqueries)
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > 75;
Ergebnis:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
| Math | 75.0 |
Der Fachbereich "History" ist nicht dabei, weil der Durchschnitt dort unter 75 liegt. Easy, oder? Jetzt bringen wir ein bisschen Magie mit Subqueries rein. Im nächsten Beispiel filtern wir zum Beispiel im Vergleich zum Gesamtdurchschnitt aller Fachbereiche.
Subqueries in HAVING
Subqueries in HAVING sind eine super Möglichkeit, mehr Flexibilität beim Filtern von aggregierten Daten zu bekommen. Damit kannst du Aggregatwerte wie Durchschnitt oder Maximum mit berechneten Werten aus anderen Teilen der Datenbank vergleichen. Kurz gesagt: Du kannst checken, ob dein Ergebnis "besser als der Durchschnitt" ist.
Beispiel: Fachbereiche nach Durchschnittsnote filtern
Angenommen, wir wollen die Fachbereiche finden, in denen die Studierenden besser als der Rest sind – also wo der Durchschnitt höher als der Uni-Durchschnitt ist.
Hier sind unsere Daten:
Tabelle students:
| student_id | student_name | department | grade |
|---|---|---|---|
| 1 | Alex | Physics | 80 |
| 2 | Maria | Physics | 85 |
| 3 | Dan | Math | 90 |
| 4 | Lisa | Math | 60 |
| 5 | John | History | 70 |
Erstmal holen wir uns den Durchschnitt aller Studierenden:
SELECT AVG(grade) AS university_avg
FROM students;
Jetzt nutzen wir eine Subquery in HAVING:
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);
Ergebnis:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
Was passiert hier?
- Die Subquery (
SELECT AVG(grade) FROM students) berechnet den Uni-Durchschnitt – hier ist das 77. - Der Hauptquery gruppiert die Studierenden nach Fachbereich und berechnet für jeden den Durchschnitt.
HAVINGvergleicht den Durchschnitt des Fachbereichs mit dem Uni-Durchschnitt und gibt nur die Fachbereiche aus, die drüber liegen.
Vergleich: WHERE vs. HAVING
Um den Unterschied zu verstehen, stell dir vor, du willst nur die Studierenden auswählen, deren Noten über dem Durchschnitt liegen. Das geht nur mit WHERE:
SELECT name, grade
FROM students
WHERE grade > (SELECT AVG(grade) FROM students);
Ergebnis (wenn wir die Tabelle von oben nehmen):
| name | grade |
|---|---|
| Alex | 80 |
| Maria | 85 |
| Dan | 90 |
Wenn du aber wissen willst, in welchen Fachbereichen der Durchschnitt der Studierenden über dem Uni-Durchschnitt liegt, kommst du ohne HAVING nicht aus – weil du nicht Zeilen, sondern Gruppen filterst:
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);
Ergebnis:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
Kurz gesagt:
WHEREarbeitet mit einzelnen Zeilen vor der Gruppierung.HAVINGfiltert Gruppen, nachdem sie aggregiert wurden.
Beispiel: Arbeiten mit mehreren Aggregaten
Schauen wir uns noch einen Fall an. Angenommen, wir haben die Tabelle students, in der Noten und Fachbereiche der Studierenden gespeichert sind:
Tabelle students:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
Jetzt wollen wir die Fachbereiche finden, wo:
- Der Durchschnitt der Noten höher als der Uni-Durchschnitt ist.
- Die Maximalnote im Fachbereich über 90 liegt.
Dafür schreiben wir diesen Query:
SELECT department, AVG(grade) AS avg_grade, MAX(grade) AS max_grade
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
AND MAX(grade) > 90;
Was passiert in diesem Query:
AVG(grade)> (SELECT AVG(grade) FROM students) – wir checken, ob der Fachbereich im Schnitt besser ist als der Rest.MAX(grade)> 90 – es gibt jemanden, der richtig abgeräumt hat.
Ergebnis:
| department | avg_grade | max_grade |
|---|---|---|
| Math | 92.5 | 95 |
Der Fachbereich "Math" ist der einzige, der sowohl einen höheren Durchschnitt als auch eine Top-Note über 90 hat.
Beispiel: Gruppen mit minimaler Abweichung auswählen
Angenommen, du willst Gruppen finden, bei denen die Differenz zwischen der höchsten und niedrigsten Note kleiner ist als die Differenz in der Uni insgesamt.
Hier ist die Tabelle students, mit der wir arbeiten:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
Wir teilen die Aufgabe in Schritte:
- Erst berechnen wir die Differenz Max-Min für die ganze Uni:
SELECT MAX(grade) - MIN(grade) AS range_university FROM students; - Jetzt bauen wir den Hauptquery und verbinden ihn mit dieser Subquery:
SELECT department, MAX(grade) - MIN(grade) AS range_department
FROM students
GROUP BY department
HAVING (MAX(grade) - MIN(grade)) < ( SELECT MAX(grade) - MIN(grade) FROM students );
Ergebnis des Queries:
| department | range_department |
|---|---|
| Physics | 5 |
| Math | 5 |
Die Gruppen "Physics" und "Math" haben stabilere Noten – deren Streuung ist kleiner als im Uni-Schnitt.
Optimierung von Queries mit HAVING und Subqueries
Denk dran: Verschachtelte Subqueries können die Performance stark beeinflussen, besonders bei großen Datenbanken. Hier ein paar Tipps:
Nutze Indizes. Wenn die Subquery auf einer Spalte läuft, die in WHERE oder JOIN verwendet wird, stell sicher, dass es dort einen Index gibt.
Vermeide Datenüberflutung. Wenn die Subquery zu viele Zwischenergebnisse liefert, teile sie in Schritte auf oder nutze temporäre Tabellen.
Profilier deine Queries mit EXPLAIN. Schau immer nach, wie PostgreSQL deinen Query ausführt. Wenn du siehst, dass die Subquery mehrfach ausgeführt wird, denk über Optimierung nach.
Vergleiche mit CTE. In manchen Fällen ist WITH (Common Table Expressions) schneller und lesbarer. Aber dazu mehr in den nächsten Vorlesungen :P
Kombinieren von Subqueries, HAVING und GROUP BY
Mit Subqueries in HAVING kannst du noch komplexere Filter bauen, besonders wenn du gleichzeitig Aggregatwerte, Durchschnitte und andere Metriken berücksichtigen willst. Das hilft dir, spannende Insights in echten Daten zu finden.
Beispiel: Vergleich von Fachbereichen nach Durchschnittsnote und Studierendenzahl
Angenommen, du willst Fachbereiche auswählen, wo:
- Der Durchschnitt über dem Uni-Durchschnitt liegt.
- Die Anzahl der Studierenden größer ist als im Fachbereich mit dem niedrigsten Durchschnitt.
Hier die Ausgangstabelle students:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
| Oleg | 60 | History |
Query:
SELECT department, AVG(grade) AS avg_grade, COUNT(*) AS student_count
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
AND COUNT(*) > (
SELECT COUNT(*)
FROM students
GROUP BY department
ORDER BY AVG(grade)
LIMIT 1
);
Dieser Query zeigt, wie man Subqueries in HAVING und GROUP BY kombiniert, um nach mehreren Kriterien zu analysieren. Ergebnis:
| department | avg_grade | student_count |
|---|---|---|
| Physics | 82.5 | 2 |
| Math | 92.5 | 2 |
Der Fachbereich History ist nicht dabei, weil er den niedrigsten Durchschnitt und die wenigsten Studierenden hat. Physics und Math – beide sind über dem Durchschnitt, sowohl bei den Noten als auch bei der Anzahl.
Typische Fehler und wie man sie vermeidet
Fehler mit NULL. Wenn deine Daten NULL enthalten, können Subqueries mit HAVING unerwartete Ergebnisse liefern. Nutze COALESCE, um solche Fälle zu behandeln:
SELECT AVG(grade)
FROM students
WHERE grade IS NOT NULL;
Zu viele Daten in der Subquery. Wenn die Subquery zu viele Ergebnisse liefert, leidet die Performance. Präzisiere immer die Bedingungen in der Subquery.
Falsches Verständnis der Ausführungsreihenfolge. Denk dran: HAVING läuft nach der Gruppierung, aber Subqueries können schon vor dem Hauptquery laufen.
Fehlende Indizes. Wenn Spalten, die in der Subquery genutzt werden, nicht indiziert sind, wird der Query deutlich langsamer.
Subqueries in HAVING eröffnen dir viele Möglichkeiten, Daten auf Aggregat-Ebene zu analysieren. Du kannst Gruppen nach komplexen Bedingungen filtern, Ergebnisse zwischen Gruppen vergleichen und richtig coole analytische Queries bauen. Glückwunsch, jetzt bist du bereit, das in echten Projekten einzusetzen!
GO TO FULL VERSION