SQL GROUP BY i Funkcje Agregujące (COUNT, SUM, AVG)
Do tej pory wiesz, jak pobierać dane (`SELECT`) i je łączyć (`JOIN`). Ale co, jeśli zadanie nie pyta 'kto kupił produkt', tylko 'ILE produktów kupił KAŻDY klient?' Albo 'jaka jest ŚREDNIA cena w KAŻDEJ kategorii?' Do tego służy `GROUP BY`. Jest to jedna z najpotężniejszych koncepcji w SQL, pozwalająca 'zwinąć' (zagregować) wiele wierszy w jeden wiersz podsumowujący. Zamiast widzieć 1000 pojedynczych zamówień, możesz zobaczyć 5 wierszy ze *sumą* zamówień dla każdego z 5 klientów. Na maturze zadania z `GROUP BY` pojawiają się *bardzo często*.
Dlaczego to ważne? Na maturze zadania typu 'policz...', 'znajdź sumę...', 'oblicz średnią dla każdego...' to standard. `GROUP BY` w połączeniu z funkcjami agregującymi (`COUNT`, `SUM`, `AVG`) to Twój podstawowy zestaw narzędzi do tworzenia podsumowań i statystyk, o które CKE prosi w niemal każdym arkuszu z bazą danych.
Teoria
Klauzula `GROUP BY` grupuje wiersze, które mają te same wartości w określonych kolumnach, w jeden wiersz podsumowujący. Prawie zawsze używa się jej z funkcjami agregującymi, które wykonują obliczenia na każdej grupie.
Jak to działa?
- Funkcje Agregujące (co liczymy):
- `COUNT(*)` lub `COUNT(kolumna)`: Zlicza wiersze w grupie.
- `SUM(kolumna)`: Oblicza sumę wartości w danej kolumnie dla grupy.
- `AVG(kolumna)`: Oblicza średnią arytmetyczną w grupie.
- `MIN(kolumna)`: Znajduje najmniejszą wartość w grupie.
- `MAX(kolumna)`: Znajduje największą wartość w grupie.
- Klauzule (jak liczymy):
- `GROUP BY [kolumna]` - Mówi bazie, jak tworzyć grupy. Np. `GROUP BY klasa` stworzy osobne 'worki' na uczniów z '1A', '1B', '2A' itd.
- `HAVING [warunek]` - To jest 'WHERE' dla grup. Filtruje wyniki *po* zgrupowaniu. Np. `HAVING COUNT(*) > 20` pokaże tylko te klasy, które mają więcej niż 20 uczniów.
- Złota Zasada SQL: Jeśli w zapytaniu jest `GROUP BY`, to w klauzuli `SELECT` mogą znaleźć się *tylko*: 1) kolumny wymienione w `GROUP BY` lub 2) funkcje agregujące.
Złożoność: Nie dotyczy w sensie O(n). Wydajność zależy od indeksowania kolumny, po której grupujemy. Na potrzeby matury, każde poprawnie napisane zapytanie `GROUP BY` będzie wystarczająco szybkie.
Implementacja
COUNT(*) - Zliczanie wierszy w grupach
SQL-- Tabela: Uczniowie (id, imie, klasa)
-- Pytanie: Ilu uczniów jest w każdej klasie?
SELECT klasa, COUNT(*) AS LiczbaUczniow
FROM Uczniowie
GROUP BY klasa;`GROUP BY klasa` tworzy grupy ('1A', '1B', '2A'...). `COUNT(*)` zlicza, ile wierszy (uczniów) wpadło do każdej grupy. `AS` pozwala nadać ładną nazwę nowej kolumnie.
SUM() i AVG() - Sumowanie i obliczanie średniej
SQL-- Tabela: Produkty (id, nazwa, kategoria, cena)
-- Pytanie: Jaka jest łączna wartość i średnia cena produktów w każdej kategorii?
SELECT kategoria, SUM(cena) AS LacznaWartosc, AVG(cena) AS SredniaCena
FROM Produkty
GROUP BY kategoria;Podobnie, grupujemy po kategorii. `SUM(cena)` dodaje ceny wszystkich produktów w danej grupie (np. 'Nabiał'), a `AVG(cena)` liczy ich średnią.
MIN() i MAX() - Znajdowanie skrajnych wartości
SQL-- Tabela: Oceny (id_ucznia, przedmiot, ocena)
-- Pytanie: Jaka jest najlepsza i najgorsza ocena z każdego przedmiotu?
SELECT przedmiot, MIN(ocena) AS Najgorsza, MAX(ocena) AS Najlepsza
FROM Oceny
GROUP BY przedmiot;Grupujemy po przedmiocie. `MIN(ocena)` i `MAX(ocena)` znajdują najmniejszą i największą wartość w kolumnie 'ocena' dla każdej oddzielnej grupy (przedmiotu).
Klauzula HAVING - Filtrowanie *po* grupowaniu
SQL-- Pytanie: Pokaż klasy, które mają WIĘCEJ niż 30 uczniów.
SELECT klasa, COUNT(*) AS LiczbaUczniow
FROM Uczniowie
GROUP BY klasa
HAVING COUNT(*) > 30;Nie możemy tu użyć `WHERE COUNT(*) > 30`, ponieważ `WHERE` filtruje wiersze *przed* grupowaniem. `HAVING` jest stworzone do filtrowania grup *po* tym, jak `COUNT(*)` je policzy.
Przykładowe Zadania Maturalne
Masz bazę danych 'Sklep', a w niej tabelę 'Zamowienia' (id_zamowienia, id_klienta, kwota). Napisz zapytanie SQL, które wyświetli identyfikatory `id_klienta` oraz *łączną sumę* wydatków (`kwota`) dla każdego klienta. Pomiń klientów, których łączna suma wydatków jest mniejsza niż 1000 zł.
Wskazówka: Potrzebujesz `GROUP BY id_klienta`, aby stworzyć grupy dla każdego klienta. Użyj `SUM(kwota)`, aby policzyć sumę dla każdej grupy. Na koniec użyj `HAVING`, aby przefiltrować grupy i pokazać tylko te z sumą > 1000.
Pokaż szkic rozwiązania
1. Wybierz klienta i sumę jego kwot: `SELECT id_klienta, SUM(kwota) AS LacznaSuma` 2. Wskaż tabelę: `FROM Zamowienia` 3. Zgrupuj wiersze per klient: `GROUP BY id_klienta` 4. Przefiltruj grupy (nie wiersze!): `HAVING SUM(kwota) > 1000;` 5. Pełne zapytanie: `SELECT id_klienta, SUM(kwota) AS LacznaSuma FROM Zamowienia GROUP BY id_klienta HAVING SUM(kwota) > 1000;`
W bazie danych znajdują się tabele 'Urzadzenia' (kod_u, nazwa_u, typ_u) oraz 'Instalacje' (data_i, kod_u, kod_k). Napisz zapytanie, które dla każdego typu urządzenia (`typ_u`) poda liczbę instalacji aplikacji na tym typie urządzenia.
Wskazówka: To zadanie wymaga złączenia (JOIN) dwóch tabel, a następnie grupowania (GROUP BY) i zliczania (COUNT). Musisz połączyć 'Urzadzenia' z 'Instalacje' przez `kod_u`. Następnie zgrupuj wyniki po `typ_u` i policz (`COUNT`) liczbę instalacji w każdej grupie.
Pokaż szkic rozwiązania
1. Wybierz typ urządzenia i liczbę wierszy: `SELECT U.typ_u, COUNT(*)` 2. Wskaż tabelę bazową: `FROM Instalacje AS I` 3. Dołącz tabelę z typami: `JOIN Urzadzenia AS U ON I.kod_u = U.kod_u` 4. Zgrupuj wyniki po typie: `GROUP BY U.typ_u;` 5. Pełne zapytanie: `SELECT U.typ_u, COUNT(*) AS LiczbaInstalacji FROM Instalacje AS I JOIN Urzadzenia AS U ON I.kod_u = U.kod_u GROUP BY U.typ_u;`
Częste Błędy
❌ Mylenie WHERE z HAVING
To najczęstszy błąd. Uczeń pisze `WHERE COUNT(*) > 10`. To nie zadziała. `WHERE` filtruje wiersze *przed* grupowaniem, a `COUNT` jest obliczany *w trakcie* grupowania.
Poprawka: Pamiętaj: `WHERE` filtruje *przed* `GROUP BY` (dane wejściowe). `HAVING` filtruje *po* `GROUP BY` (wyniki grupowania). Poprawnie: `HAVING COUNT(*) > 10`.
❌ Błąd 'non-aggregated column'
Piszesz: `SELECT klasa, imie, COUNT(*) FROM Uczniowie GROUP BY klasa;`. To błąd. Baza nie wie, które 'imie' ma wyświetlić dla grupy '1A' (jest ich 30).
Poprawka: Złota Zasada: Wszystko, co jest w `SELECT`, musi być albo funkcją agregującą (np. `COUNT(*)`, `AVG(imie)` - co nie ma sensu), albo musi być w klauzuli `GROUP BY`.
❌ Zapominanie o `GROUP BY`
Piszesz: `SELECT klasa, COUNT(*) FROM Uczniowie;`. Wiele baz danych zwróci błąd, a inne (jak MySQL) mogą zwrócić bezsensowny wynik (jedną klasę i łączną liczbę *wszystkich* uczniów).
Poprawka: Jeśli używasz funkcji agregującej (jak `COUNT`) i chcesz ją policzyć dla *każdej* `klasy`, musisz dodać `GROUP BY klasa`.
❌ Różnica między `COUNT(*)` a `COUNT(kolumna)`
`COUNT(*)` zlicza wszystkie wiersze w grupie. `COUNT(kolumna)` zlicza tylko te wiersze w grupie, w których `kolumna` *nie jest pusta* (nie jest `NULL`).
Poprawka: Jeśli chcesz policzyć 'ilu uczniów jest w klasie', bezpieczniej użyć `COUNT(*)`. Jeśli chcesz policzyć 'ilu uczniów ma wpisanego maila', użyj `COUNT(email)`.
Kluczowe Wnioski
- `GROUP BY` służy do 'zwijania' wielu wierszy w jeden wiersz podsumowujący (np. per klasa, per kategoria).
- Używa się go *zawsze* z funkcjami agregującymi: `COUNT` (zlicz), `SUM` (sumuj), `AVG` (średnia), `MIN`/`MAX`.
- Złota Zasada: Kolumny w `SELECT` muszą albo być w `GROUP BY`, albo być funkcją agregującą.
- `WHERE` filtruje wiersze *przed* grupowaniem.
- `HAVING` filtruje grupy *po* grupowaniu (np. `HAVING COUNT(*) > 5`).
- Kolejność wykonywania to: `FROM` -> `WHERE` -> `GROUP BY` -> `HAVING` -> `SELECT` -> `ORDER BY`.
Najczęściej Zadawane Pytania
❓ Jaka jest dokładnie różnica między WHERE a HAVING?
`WHERE` działa na pojedynczych wierszach, *zanim* zostaną one połączone w grupy. `HAVING` działa na całych grupach, *po* tym jak zostaną utworzone i policzone (np. przez `COUNT`). `WHERE klasa = '1A'` jest OK, `WHERE COUNT(*) > 10` jest źle. `HAVING COUNT(*) > 10` jest OK.
❓ Czy mogę użyć `GROUP BY` bez funkcji agregującej?
Technicznie tak (`SELECT klasa FROM Uczniowie GROUP BY klasa;`), ale nie ma to sensu. Zwróci to po prostu unikalne nazwy klas, co można szybciej osiągnąć przez `SELECT DISTINCT klasa FROM Uczniowie;`. `GROUP BY` jest stworzone do pracy z agregatami.
❓ Czy mogę grupować po wielu kolumnach?
Tak. `GROUP BY kraj, miasto` stworzy grupy dla każdej unikalnej kombinacji kraju i miasta (np. 'Polska, Warszawa', 'Polska, Kraków', 'Niemcy, Berlin').
❓ Co to jest `COUNT(DISTINCT kolumna)`?
To specjalna forma `COUNT`. `COUNT(kolumna)` zliczy wszystkie wystąpienia. `COUNT(DISTINCT kolumna)` zliczy tylko *unikalne* wartości w tej kolumnie. Np. `COUNT(DISTINCT klasa)` policzy, ile jest różnych klas.
Chcesz opanować wszystkie tematy maturalne?
Dołącz do kursu i zyskaj dostęp do interaktywnych lekcji, edytora kodu i setek zadań.