Czasem nie wystarczy tylko pogrupować dane i przefiltrować wynik, ale trzeba to zrobić z dodatkową logiką — na przykład porównać średnią ocenę studentów w grupie z jakimś zewnętrznym kryterium. Wtedy na scenę wchodzi HAVING z subquery — mega narzędzie, które pozwala podejmować mądrzejsze decyzje już w samym zapytaniu SQL.
Przypominamy sobie HAVING
Skupmy się na subquery używanych razem z HAVING, żeby filtrować dane na poziomie wartości zagregowanych. Po co? Jeśli WHERE pozwala filtrować pojedyncze wiersze, to HAVING działa już na zgrupowanych danych — to inny poziom analizy, który daje ci więcej możliwości.
Zanim zanurzymy się w połączenie subquery i HAVING, przypomnijmy sobie, czym jest HAVING i czym różni się od WHERE.
WHEREfiltruje wiersze przed wykonaniem grupowania (GROUP BY).HAVINGfiltruje dane po agregacji, kiedy dane są już pogrupowane.
Wyobraź sobie, że analizujesz studentów i ich oceny. Dzięki WHERE możesz wykluczyć studentów z określonymi minimalnymi ocenami, a HAVING pozwoli ci wykluczyć całe grupy studentów na podstawie ich średniej lub maksymalnej oceny.
Przykład danych
Oto przykładowa tabela studentów:
Tabela 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 |
Przykład użycia HAVING (bez subquery)
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > 75;
Wynik:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
| Math | 75.0 |
Kierunek "History" nie znalazł się w wyniku, bo średnia ocena jest tam niższa niż 75. Proste, nie? Teraz dorzucimy trochę magii z subquery. W następnym przykładzie możemy np. filtrować względem ogólnej średniej dla wszystkich kierunków.
Subquery w HAVING
Subquery w HAVING — to świetna opcja na większą elastyczność przy filtrowaniu danych zagregowanych. Pozwalają porównywać agregaty, takie jak średnia ocena czy maksimum, z wyliczonymi wartościami z innych części bazy. Mówiąc prościej, możesz sprawdzić: "Czy nasz wynik jest lepszy niż średnia w całym uniwerku?"
Przykład: filtrowanie kierunków po średniej ocenie
Załóżmy, że chcemy znaleźć te kierunki, gdzie studenci uczą się lepiej niż reszta — czyli średnia ocena na kierunku jest wyższa niż średnia na całym uniwersytecie.
Oto nasze dane:
Tabela 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 |
Najpierw wyciągniemy średnią ocenę dla wszystkich studentów:
SELECT AVG(grade) AS university_avg
FROM students;
Teraz użyjemy subquery w HAVING:
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);
Wynik:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
Co tu się dzieje?
- Subquery (
SELECT AVG(grade) FROM students) wylicza ogólną średnią ocenę — w tym przypadku to 77. - Główne zapytanie grupuje studentów po kierunkach i liczy dla każdego średnią ocenę.
HAVINGporównuje średnią ocenę kierunku z ogólną średnią i przepuszcza tylko te kierunki, gdzie wynik jest wyższy.
Porównanie użycia WHERE i HAVING
Żeby zrozumieć różnicę, wyobraź sobie, że chcesz wybrać tylko tych studentów, którzy mają ocenę wyższą niż średnia. To można zrobić tylko przez WHERE:
SELECT name, grade
FROM students
WHERE grade > (SELECT AVG(grade) FROM students);
Wynik (jeśli użyć tabeli z poprzednich przykładów):
| name | grade |
|---|---|
| Alex | 80 |
| Maria | 85 |
| Dan | 90 |
A jeśli chcesz zobaczyć, na których kierunkach średnia ocen studentów jest wyższa niż średnia na uniwerku, to bez HAVING się nie obejdzie — bo filtrujesz nie wiersze, tylko grupy:
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);
Wynik:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
W skrócie:
WHEREdziała na pojedynczych wierszach przed grupowaniem.HAVINGfiltruje grupy po tym, jak są zagregowane.
Przykład: praca z wieloma agregatami
Przeanalizujmy jeszcze jeden przypadek. Załóżmy, że mamy tabelę students, w której są dane o ocenach studentów i ich kierunkach:
Tabela students:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
Teraz chcemy znaleźć kierunki, gdzie:
- Średnia ocena studentów jest wyższa niż średnia na uniwerku.
- Maksymalna ocena na kierunku jest większa niż 90.
Do tego napiszemy takie zapytanie:
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;
Co się dzieje w tym zapytaniu:
AVG(grade)> (SELECT AVG(grade) FROM students) — sprawdzamy, czy kierunek jest średnio mocniejszy od reszty.MAX(grade)> 90 — czyli jest tam ktoś, kto świetnie zdał egzamin.
Wynik:
| department | avg_grade | max_grade |
|---|---|---|
| Math | 92.5 | 95 |
Kierunek "Math" okazał się jedynym, który jednocześnie ma średnią ocenę wyższą od ogólnej i wybitnego studenta z oceną powyżej 90.
Przykład: wybór grup z minimalnym odchyleniem
Załóżmy, że chcesz znaleźć grupy, gdzie różnica między maksymalną a minimalną oceną studentów jest mniejsza niż różnica na uniwerku ogólnie.
Oto tabela students, na której będziemy pracować:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
Podzielmy zadanie na etapy:
- Najpierw policzymy różnicę maksimum-minimum dla całego uniwerku:
SELECT MAX(grade) - MIN(grade) AS range_university FROM students; - Teraz stworzymy główne zapytanie i połączymy je z tym 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 );
Wynik zapytania:
| department | range_department |
|---|---|
| Physics | 5 |
| Math | 5 |
Grupy "Physics" i "Math" miały bardziej stabilne oceny — ich rozrzut jest mniejszy niż na uniwerku ogólnie.
Optymalizacja zapytań z HAVING i subquery
Pamiętaj, że zagnieżdżone subquery mogą mocno wpływać na wydajność, szczególnie w dużych bazach danych. Kilka tipów:
Używaj indeksów. Jeśli subquery działa na kolumnie, która jest w WHERE albo JOIN, upewnij się, że ta kolumna ma indeks.
Unikaj przeładowania danymi. Jeśli subquery zwraca za dużo wyników pośrednich, rozbij je na etapy albo użyj tymczasowych tabel.
Profiluj zapytania przez EXPLAIN. Zawsze sprawdzaj, jak PostgreSQL wykonuje twoje zapytanie. Jeśli widzisz, że subquery odpala się wielokrotnie, pomyśl o optymalizacji.
Porównaj z CTE. W niektórych przypadkach użycie WITH (Common Table Expressions) może być szybsze i wygodniejsze do czytania. Ale o tym pogadamy w kolejnych wykładach :P
Kombinowanie subquery, HAVING i GROUP BY
Dzięki subquery w HAVING możesz budować bardziej złożone filtry, szczególnie gdy trzeba jednocześnie brać pod uwagę agregaty, średnie i inne metryki. To wszystko pomaga wyciągać ciekawe insighty z realnych danych.
Przykład: porównanie kierunków po średniej ocenie i liczbie studentów
Załóżmy, że chcesz wybrać kierunki, gdzie:
- Średnia ocena jest wyższa niż średnia na uniwerku.
- Liczba studentów jest większa niż na kierunku z najniższą średnią oceną.
Oto wyjściowa tabela 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 |
Zapytanie:
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
);
To zapytanie pokazuje możliwości łączenia subquery w HAVING i GROUP BY do analizy według kilku kryteriów naraz. Wynik:
| department | avg_grade | student_count |
|---|---|---|
| Physics | 82.5 | 2 |
| Math | 92.5 | 2 |
Kierunek History nie znalazł się w wyniku, bo ma najniższą średnią ocenę i najmniej studentów. Physics i Math — oba powyżej średniej i pod względem ocen, i liczby studentów.
Typowe błędy i jak ich unikać
Błąd z NULL. Jeśli dane zawierają NULL, subquery z HAVING mogą zwrócić nieoczekiwane wyniki. Używaj COALESCE, żeby to ogarnąć:
SELECT AVG(grade)
FROM students
WHERE grade IS NOT NULL;
Nadmiarowe dane w subquery. Jeśli subquery zwraca za dużo wyników, odbije się to na wydajności. Zawsze doprecyzuj warunki subquery.
Złe zrozumienie kolejności wykonania. Pamiętaj, że HAVING wykonuje się po grupowaniu, a subquery mogą wykonać się przed głównym zapytaniem.
Brak indeksów. Jeśli kolumny używane w subquery nie mają indeksów, zapytanie będzie dużo wolniejsze.
Subquery w HAVING otwierają ci masę możliwości do analizy danych na poziomie agregatów. Możesz filtrować grupy po złożonych warunkach, porównywać wyniki między grupami i budować zaawansowane zapytania analityczne. Gratulacje, teraz jesteś gotowy, żeby używać tej wiedzy w prawdziwych projektach!
GO TO FULL VERSION