GROUP BY, HAVING
Dla potrzeb przykładółw utworzymy tablicę danych osobowych uczniów z różnych miast w
Polsce.
+------------------------------------------------------------------+
| Imię Nazwisko Miasto Data_urodzenia Telefon Płeć |
+------------------------------------------------------------------+
| Jan Łukomski Warszawa 1990-12-10 826-44-56 M |
| Anna Wierzbicka Kraków 1987-2-21 621-66-10 K |
| ... ... ... ... ... ... |
+------------------------------------------------------------------+
Opcja
GROUP BY
Opcja grupujca w zdaniu
SELECT
Wstawia się ją do zdania
SELECT
po opcji
WHERE
(lub
po opcji
FROM
, jeśli
WHERE
nie użyto), ale przed
ORDER BY
.
Przykłady:
Załóżmy, że chcemy znaleźć liczby uczniów z poszczególnych miast. Np. liczbę uczniów z
Gdańska można uzyskać łatwo:
mysql> SELECT COUNT(*) FROM Uczniowie WHERE Miasto='Gdańsk';
+---+
| 1 |
+---+
Podobnie można zrobić dla Krakowa, Warszawy i innych miast. Gorzej jest, jeśli chcemy
dokonać zbiorczych obliczeń dla różnych miast, tj. mieć listę miast i przy każdym - liczbę
mieszkających w nim uczniów. Do rozwiązania tego zadania trzeba użyć innej opcji zdania
SELECT
-
GROUP BY
.
mysql> SELECT Miasto, COUNT(*) FROM Uczniowie GROUP BY Miasto
ORDER BY 2 DESC;
+----------------------+
| Miasto | COUNT(*) |
+----------------------+
| Warszawa | 6 |
| Kraków | 3 |
| Gdynia | 2 |
| Bydgoszcz | 1 |
| Tykocin | 1 |
| Otwock | 1 |
| Szczecin | 1 |
+----------------------+
Opcja
GROUP BY
("grupuj wynik wg") pozwala użyć funkcji agregujących (
SUM, AVG,
MAX, MIN, COUNT
) nie do całej tabeli, ale do grup wierszy w tabeli wynikowej. Innymi
słowy:
GROUP BY
określa zakres działania funkcji np.
COUNT
- tu działa ona dla każdego
miasta niezależnie (podobnie jest dla innych funkcji agregujących). Bez opcji
GROUP BY
funkcja
COUNT
, a także pozostałe funkcje agregujące, działają na całej tabeli (na wszystkich
wierszach). Dla każdej grupy, tu: dla każdego miasta,
GROUP BY
generuje jeden wiersz.
Jeśli, oprócz funkcji agregującej, poda się w zapytaniu także inne kolumny, ale nie użyje się
opcji
GROUP BY
, zostanie zasygnalizowany błąd.
Tak będzie np. w przypadku zapytania:
mysql> SELECT Miasto, COUNT(*) FROM Uczniowie;
mysql> Błąd nr 1140:
Mieszanie funkcji grupowych (MIN(),MAX(),COUNT()...), bez określenia
grupowania.
Przy próbie jej wykonania wypisany zostanie komunikat o mieszaniu kolumn z funkcjami
agregującymi, niedopuszczalnym bez użycia
GROUP BY
. Jest to naturalne - nie bardzo
wiadomo jak należałoby wypisać (potraktować) wynik powyższego zapytania bez
grupowania wyników (które miasto i jaką liczbę podać).
Przykłady:
1. Wypisz listę miast, z liczbą uczennic z każdego miasta, uporządkowaną malejąco wg
liczby uczennic.
mysql> SELECT Miasto, COUNT(*) FROM Uczniowie WHERE Płeć='K'
GROUP BY Miasto ORDER BY 2 DESC;
2. Wypisz dla każdego miasta jego nazwę oraz datę urodzenia najmłodszej uczennicy.
mysql> SELECT Miasto, MAX(Data_ur) AS Data FROM Uczniowie
WHERE Płeć='K' GROUP BY Miasto;
3. Napisz zapytanie, z którego dostaniemy numery miesięcy, z liczbami uczniów, którzy
urodzili się w tym miesiącu, uporządkowaną malejąco wg liczby uczniów, a w obrębie
tej samej liczby urodzeń - wg numerów miesięcy, rosnąco. Kolumny mają być
zatytułowane 'Miesiąc' i 'Liczba_uczniów'.
mysql> SELECT MONTH(Data_urodzenia) AS Miesiąc, COUNT(*) AS
Liczba_uczniów FROM Uczniowie GROUP BY 1 ORDER BY 2 DESC, 1
ASC;
Załóżmy teraz, że chcemy wybrać tylko te miasta, z których pochodzą co najmniej dwie
uczennice. Możemy spróbować warunek wstawić do
WHERE
:
mysql> SELECT Miasto, COUNT(*) FROM Uczniowie WHERE Płeć='K'
AND COUNT(*) >= 2 GROUP BY Miasto ORDER BY 2 DESC;
mysql> Błąd nr 1111:
Próba wykonania zapytania daje błąd. Jest on naturalny: opcja
WHERE
określa warunki na
rekordy, a nie na wynik ich przeszukiwania (dopiero na podstawie tego wyniku można
określić, dla którego miasta liczba uczennic jest większa lub równa 2). Sprawę rozwiązuje
użycie opcji
HAVING
, będącej dodatkowym filtrem nakładanym na wynik działania
GROUP
BY
:
mysql> SELECT Miasto, COUNT(*) FROM Uczniowie GROUP BY Miasto
HAVING COUNT(*) >= 2 ORDER BY 2 DESC;
+----------------------+
| Miasto | COUNT(*) |
+----------------------+
| Warszawa | 6 |
| Kraków | 3 |
| Gdynia | 2 |
+----------------------+
Załóżmy teraz, że chcemy wybrać tylko te miasta, z których pochodzą co najmniej dwie
uczennice. Możemy spróbować warunek wstawić do WHERE:
mysql> SELECT Miasto, COUNT(*) FROM Uczniowie WHERE Płeć='K'
AND COUNT(*) >= 2 GROUP BY Miasto ORDER BY 2 DESC;
Błąd nr 1111:
Błędne użycie funkcji grupowych.
Próba wykonania zapytania daje błąd. Jest on naturalny: opcja WHERE określa warunki na
rekordy, a nie na wynik ich przeszukiwania (dopiero na podstawie tego wyniku można
określić, dla którego miasta liczba uczennic jest większa lub równa 2). Sprawę rozwiązuje
użycie opcji HAVING, będącej dodatkowym filtrem nakładanym na wynik działania GROUP
BY:
mysql> SELECT Miasto, COUNT(*) FROM Uczniowie GROUP BY Miasto
HAVING COUNT(*) >= 2 ORDER BY 2 DESC;
+----------------------+
| Miasto | COUNT(*) |
+----------------------+
| Warszawa | 6 |
| Kraków | 3 |
| Gdynia | 2 |
+----------------------+
Gdyby chodziło o miasta, z których pochodzi tylko jeden uczeń, warunek w HAVING byłby
COUNT(*)=1.
Podstawowa różnica między WHERE i HAVING polega na tym, kiedy w procesie tworzenia
tabeli wynikowej opcje te są wykorzystywane. Otóż WHERE służy do określenia, które
wiersze z tabeli wybrać, HAVING zaś 'działa' na rezultacie wyboru (już po zastosowaniu
WHERE). Tę różnicę widać w zapytaniach.
Następne zapytanie
mysql> SELECT COUNT(*) FROM Uczniowie WHERE Miasto LIKE 'W%';
+----------+
| COUNT(*) |
+----------+
| 7 |
+----------+
wykona się poprawnie (liczba uczniów mieszkających w miastach o nazwach zaczynających
się na literę W. WHERE wyszuka takie rekordy w tabeli Uczniowie). Błędne jest natomiast
natomiast:
mysql> SELECT COUNT(*) FROM Uczniowie HAVING Miasto LIKE
'W%';
Błąd nr 1054:
Nieznana kolumna.
Błąd - nieznana kolumna. W wyniku zrealizowanej selekcji - a na tym wyniku działa HAVING
- nie ma kolumny 'Miasto' jest tylko liczba rekordów w tabeli Uczniowie.
Natomiast dwa zapytania:
mysql> SELECT Miasto, COUNT(*) FROM Uczniowie WHERE Miasto
LIKE 'W%' GROUP BY Miasto;
+----------------------+
| Miasto | COUNT(*) |
+----------------------+
| Warszawa | 6 |
| Wrocław | 1 |
+----------------------+
mysql> SELECT Miasto, COUNT(*) FROM Uczniowie GROUP BY Miasto
HAVING Miasto LIKE 'W%';
+----------------------+
| Miasto | COUNT(*) |
+----------------------+
| Warszawa | 6 |
| Wrocław | 1 |
+----------------------+
zwrócą ten sam wynik.
Kiedy nie używa się opcji GROUP BY, opcja HAVING działa identycznie jak opcja WHERE
(warunek wyboru rekordów można podać w WHERE lub w HAVING, z tym samym skutkiem
końcowym). Problem: którą z tych opcji - przy braku GROUP BY lub w przypadku, gdy obie
opcje można stosować np. do selekcji pojedynczych rekordów - wybrać? Zasadą ogólną jest:
jeśli coś można wyfiltrować za pomocą WHERE, należy kryterium wyboru podać w tej opcji.
HAVING należy używać tylko wtedy, gdy używa się opcji GROUP BY. Przyczyn tego
zalecenia jest kilka, a jedną z nich jest efektywność: kolumny wyfiltrowane za pomocą
WHERE nie biorą już udziału w przetwarzaniu opcji GROUP BY i HAVING i tym samym
ogólny czas realizacji zapytania skraca się.