• Bazy danych i SQL
  • Agregacja SQL - GROUP BY, HAVING, funkcje okienkowe. Jak to działa?

Agregacja SQL - GROUP BY, HAVING, funkcje okienkowe. Jak to działa?

Tymoteusz Kowalski 26 maja 2026
Młody programista pochyla się nad biurkiem, analizując kod na ekranach. Zastanawia się nad optymalizacją zapytań, wykorzystując funkcje agregujące SQL do efektywnego przetwarzania danych.

Spis treści

W SQL agregacja pozwala zamienić duży zbiór wierszy w jeden sensowny wynik: sumę sprzedaży, średnią ocenę, liczbę zamówień albo listę unikalnych wartości. To właśnie te operacje najczęściej decydują o tym, czy raport pokazuje realny obraz danych, czy tylko przypadkową tabelę rekordów.

W tym artykule pokazuję, jak działają funkcje agregujące w SQL, kiedy używać `GROUP BY` i `HAVING`, czym różni się `COUNT(*)` od `COUNT(kolumna)` oraz kiedy lepiej przejść na funkcje okienkowe. Dorzucam też przykłady i pułapki, które w praktyce psują wyniki częściej, niż wielu osobom się wydaje.

Najważniejsze rzeczy, które warto zapamiętać

  • Agregacja w SQL zamienia wiele wierszy w jeden wynik dla całej tabeli albo dla konkretnej grupy.
  • `WHERE` filtruje wiersze przed agregacją, a `HAVING` filtruje już gotowe grupy.
  • `COUNT(*)` liczy wiersze, a `COUNT(kolumna)` pomija wartości `NULL`.
  • `GROUP BY` ma sens tylko wtedy, gdy grupujesz po kolumnach, które naprawdę odpowiadają pytaniu biznesowemu.
  • Funkcje okienkowe zwracają wynik dla każdego wiersza, a nie jeden wynik dla całej grupy.
  • Przy dużych tabelach największe znaczenie mają czytelność zapytania, poprawne grupowanie i kontrola duplikatów po `JOIN`.

Jak działają funkcje agregujące SQL

W praktyce traktuję je jako narzędzie do odpowiedzi na pytania typu: „ile?”, „jaka średnia?”, „który rekord jest najmniejszy lub największy?”, „ile mamy unikalnych wartości?”. Silnik SQL bierze wiele wierszy, przetwarza je według określonej reguły i zwraca jedną wartość albo jedną wartość na grupę.

Najczęściej chodzi o funkcje takie jak `COUNT`, `SUM`, `AVG`, `MIN` i `MAX`, ale w wielu bazach dostępne są też agregacje tekstowe, statystyczne czy geograficzne. Ważny szczegół: większość agregatów ignoruje `NULL`, więc brak danych nie zawsze jest liczony tak samo jak zero. To różnica, która potrafi całkowicie zmienić interpretację raportu.

Warto też pamiętać, że agregacja nie jest „magiczna”. To po prostu obliczenie wykonane na zbiorze wartości. Jeśli wiersze są zdublowane przez zły `JOIN`, agregat też pokaże zafałszowany wynik. Dlatego przed sięgnięciem po sumę lub średnią zawsze sprawdzam, czy źródłowe dane są w ogóle na poziomie, na którym chcę liczyć.

Od tego punktu najważniejsze staje się już nie samo „liczenie”, ale wybór właściwej funkcji i właściwego sposobu grupowania.

Najważniejsze funkcje i kiedy używać każdej z nich

Funkcja Co zwraca Kiedy się przydaje Na co uważać
COUNT(*) Liczbę wszystkich wierszy Gdy chcesz policzyć rekordy bez względu na wartości w kolumnach Nie pomija wierszy z `NULL`
COUNT(kolumna) Liczbę niepustych wartości w kolumnie Gdy interesują Cię tylko faktycznie uzupełnione dane Pomija `NULL`, więc wynik bywa niższy niż liczba wierszy
COUNT(DISTINCT kolumna) Liczbę unikalnych wartości Do policzenia klientów, produktów albo miast bez duplikatów Może być kosztowniejsze niż zwykły `COUNT`
SUM(kolumna) Suma wartości liczbowych Do raportowania sprzedaży, kosztów, czasu pracy Ignoruje `NULL`, ale nie naprawi błędów pochodzących z duplikatów
AVG(kolumna) Średnią arytmetyczną Do oceny średniego koszyka, czasu realizacji, oceny produktu Łatwo zafałszować wynik przez skrajne wartości i błędne grupy
MIN(kolumna) / MAX(kolumna) Najmniejszą lub największą wartość Do zakresów dat, cen, wyników, ocen Na tekstach działa porządek leksykograficzny, nie „logiczny”
STRING_AGG, GROUP_CONCAT, LISTAGG Połączony tekst z wielu wierszy Do list nazw, tagów, kategorii albo miast Nazwy i składnia różnią się zależnie od bazy danych

Jeśli mam wskazać jeden praktyczny wniosek, to ten: najpierw dobieram funkcję do pytania, a dopiero później myślę o formatowaniu wyniku. Inaczej łatwo zrobić zapytanie, które wygląda poprawnie, ale odpowiada na zupełnie inne pytanie niż to, które zadał biznes.

To prowadzi prosto do `GROUP BY`, bo bez niego większość agregacji pozostaje tylko liczeniem „po całości”, a nie na poziomie interesujących nas segmentów.

Dlaczego `GROUP BY` decyduje o sensie wyniku

`GROUP BY` dzieli wiersze na podzbiory według wybranych kolumn. Dzięki temu zamiast jednego wyniku dla całej tabeli dostajesz wynik osobno dla każdego miasta, klienta, kategorii produktu albo miesiąca. W analizie danych to zwykle ważniejsze niż sama funkcja agregująca, bo to właśnie grupa mówi, co porównujesz.

Najprostszy przykład wygląda tak:

SELECT
    city,
    SUM(amount) AS revenue
FROM orders
GROUP BY city;

Jeżeli tabela `orders` ma zamówienia z Warszawy, Krakowa i Gdańska, to wynik będzie miał trzy wiersze. Każdy z nich pokaże sumę dla jednego miasta. Właśnie dlatego `GROUP BY` jest tak istotny: bez niego nie dostaniesz porównań między segmentami, tylko jedną sumę dla wszystkiego.

Warto też odróżnić `WHERE` od `HAVING`:

Element Na czym działa Moment użycia Przykład zastosowania
WHERE Na pojedynczych wierszach Przed agregacją Wykluczenie anulowanych zamówień
HAVING Na gotowych grupach Po agregacji Pokazanie tylko miast ze sprzedażą powyżej 10 000

To rozróżnienie jest krytyczne. Jeśli filtrujesz źle, wynik może być formalnie poprawny, ale logicznie bezużyteczny. Następnie pokażę na konkretnych przykładach, jak to wygląda w praktyce, bo tu najlepiej widać różnicę między dobrą a przypadkową agregacją.

Mężczyzna w okularach stoi przy biurku z monitorami wyświetlającymi wykresy i kod. Widać na nich zastosowanie funkcji agregujących SQL do analizy danych.

Przykłady, które od razu pokazują różnicę

Załóżmy prostą tabelę `orders`:

order_id customer city amount
1 Anna Warszawa 120
2 Anna Warszawa 80
3 Bartek Kraków 200
4 Celina Kraków 150
5 Damian Gdańsk 90

Jeśli chcę policzyć sprzedaż per miasto, użyję:

SELECT
    city,
    SUM(amount) AS revenue
FROM orders
GROUP BY city
ORDER BY revenue DESC;

Wynik będzie prosty: Warszawa 200, Kraków 350, Gdańsk 90. Taki raport jest wartościowy, bo od razu pokazuje, gdzie sprzedaż jest najwyższa, a gdzie najsłabsza.

Gdy interesuje mnie liczba zamówień i liczba klientów, lepszy będzie wariant z `COUNT`:

SELECT
    COUNT(*) AS all_orders,
    COUNT(amount) AS paid_orders,
    COUNT(DISTINCT customer) AS unique_customers
FROM orders;

Tu różnica jest ważna: `COUNT(*)` policzy wszystkie wiersze, `COUNT(amount)` pominie puste wartości, a `COUNT(DISTINCT customer)` pokaże, ilu mamy unikalnych klientów. W praktyce to częsty zestaw do szybkiej kontroli jakości danych i skali ruchu.

Jeśli chcę wyłapać tylko te miasta, w których sprzedaż przekroczyła 200 zł, użyję `HAVING`:

SELECT
    city,
    SUM(amount) AS revenue
FROM orders
GROUP BY city
HAVING SUM(amount) > 200;

To już nie jest zwykłe filtrowanie rekordów. Tu decyduje wynik agregacji, a nie pojedyncza wartość w wierszu. Ten przykład dobrze pokazuje, jak szybko można przejść od prostego liczenia do naprawdę użytecznego raportowania.

Skoro widać już podstawy, pora przejść do błędów, które najczęściej niszczą wiarygodność takich zapytań.

Najczęstsze błędy, które psują raporty

Błąd Co się dzieje Jak to naprawić
Mylenie `WHERE` z `HAVING` Filtr działa w złym momencie i wynik przestaje odpowiadać pytaniu Wybieraj `WHERE` dla wierszy, `HAVING` dla grup
Używanie `COUNT(kolumna)` zamiast `COUNT(*)` Braki `NULL` znikają z liczenia Sprawdź, czy liczysz rekordy, czy tylko niepuste wartości
Duplikaty po `JOIN` `SUM` i `COUNT` rosną szybciej niż powinny Agreguj na właściwym poziomie albo uprzednio zredukuj dane w CTE
Wybieranie kolumn nieujętych w `GROUP BY` Zapytanie bywa odrzucone albo zwraca przypadkowy wiersz z grupy Dodaj kolumnę do grupowania albo użyj osobnej agregacji
Agregowanie danych bez normalizacji tekstu `Warszawa`, `warszawa` i ` Warszawa ` stają się trzema różnymi grupami Oczyść dane przed raportowaniem
Zbyt wczesne zaokrąglanie średnich Różnice rozchodzą się na kolejnych poziomach analizy Zaokrąglaj na końcu, nie w środku obliczeń

Najgroźniejszy błąd to zwykle nie sama składnia, tylko nieświadome powielenie rekordów. Widziałem raporty, w których jedna faktura pojawiała się cztery razy po połączeniu z tabelą pozycji i nagle sprzedaż wyglądała na 400% wyższą niż w rzeczywistości. SQL nie ostrzeże przed takim błędem, bo dla silnika to nadal poprawne dane.

Jeżeli Twoje zapytanie ma nie tylko liczyć, ale też pokazywać wynik obok każdego rekordu, wtedy często lepsze okazują się funkcje okienkowe. I właśnie to odróżnienie warto znać.

Kiedy lepiej użyć funkcji okienkowych

Funkcje okienkowe są bliskie agregacji, ale nie robią tego samego. Agregacja zmniejsza liczbę wierszy, a funkcja okienkowa zostawia wszystkie wiersze i dokleja do nich wynik obliczenia. Dzięki temu możesz mieć jednocześnie szczegół i kontekst.

Przykład:

SELECT
    order_id,
    city,
    amount,
    SUM(amount) OVER (PARTITION BY city) AS city_total
FROM orders;

Każdy wiersz nadal istnieje, ale obok niego pojawia się suma dla całego miasta. To bardzo przydatne, gdy chcesz pokazać udział pojedynczego zamówienia w całości albo policzyć ranking bez utraty szczegółów.

Jeszcze lepiej widać to przy sumach narastających:

SELECT
    order_id,
    city,
    amount,
    SUM(amount) OVER (
        PARTITION BY city
        ORDER BY order_id
    ) AS running_total
FROM orders;

Takie zapytanie daje wynik dla każdego kolejnego rekordu. W raportach sprzedażowych, finansowych i operacyjnych to często wygodniejsze niż klasyczne `GROUP BY`, bo nie tracisz informacji o kolejnych zdarzeniach. Ja zwykle sięgam po okna wtedy, gdy klasyczna agregacja zaczyna „zjadać” zbyt dużo kontekstu.

Na końcu zostaje jeszcze jedno pytanie: jak pisać takie zapytania, żeby były czytelne i nie sprawiały problemów przy rozbudowie.

Jak pisać agregacje, które da się utrzymać

  • Używaj czytelnych aliasów, np. `revenue`, `orders_count`, `avg_rating` zamiast przypadkowych nazw.
  • Oddziel filtrację od agregacji, żeby od razu było widać, co dzieje się przed `GROUP BY`, a co po nim.
  • Jeśli zapytanie robi się długie, rozbij je na CTE lub podzapytania. To zwykle poprawia czytelność bardziej niż kolejne komentarze.
  • Sprawdzaj, czy kolumny używane w `GROUP BY` mają sens biznesowy. Grupa po złym polu daje poprawną składnię, ale błędny raport.
  • Przy dużych tabelach zwracaj uwagę na indeksy, ale nie zakładaj, że indeks rozwiąże każdy problem wydajnościowy. Czasem silnik i tak musi przeskanować duży fragment danych.
  • Jeśli raport uruchamia się wielokrotnie, rozważ tabelę agregującą albo widok materializowany zamiast liczenia wszystkiego od zera.

W praktyce najlepsze zapytania agregujące są zwykle dość proste, tylko dobrze ułożone. Nadmierna komplikacja rzadko daje lepszy wynik, a dużo częściej utrudnia późniejszą kontrolę jakości danych.

Co warto wdrożyć od razu w swoich zapytaniach

Jeśli mam wybrać kilka nawyków, które naprawdę poprawiają jakość pracy z danymi, to byłyby to: jawne `GROUP BY`, świadome użycie `COUNT`, sprawdzanie `NULL` oraz kontrola duplikatów po `JOIN`. Te cztery rzeczy rozwiązują zaskakująco dużo problemów, zanim jeszcze zaczniesz myśleć o optymalizacji.

Druga rzecz to testowanie na małym wycinku danych. Zanim zaufam zapytaniu na całej tabeli, porównuję wyniki z ręcznie policzonym fragmentem albo z prostym przekrojem 3-5 rekordów. To szybki sposób na wyłapanie błędów, których sama składnia nie pokaże. W analizie danych taka dyscyplina oszczędza więcej czasu niż późniejsze poprawki w gotowym raporcie.

FAQ - Najczęstsze pytania

Agregacja w SQL to proces przekształcania wielu wierszy danych w jeden sensowny wynik, np. sumę, średnią, liczbę unikalnych wartości. Pozwala na podsumowanie i analizę dużych zbiorów danych, co jest kluczowe w raportowaniu i podejmowaniu decyzji biznesowych.

GROUP BY służy do grupowania wierszy na podstawie wartości w jednej lub więcej kolumn, aby funkcje agregujące działały na tych grupach. HAVING natomiast filtruje te już zgrupowane wyniki, pozwalając na selekcję grup spełniających określone warunki agregacji.

COUNT(*) zlicza wszystkie wiersze w grupie, wliczając te z wartościami NULL w dowolnej kolumnie. COUNT(kolumna) zlicza tylko wiersze, w których określona kolumna nie ma wartości NULL, pomijając puste wpisy.

Funkcje okienkowe są przydatne, gdy potrzebujesz obliczeń agregujących (np. sumy, średniej, rankingu) dla podzbioru wierszy (okna), ale chcesz zachować wszystkie szczegółowe wiersze w wyniku. Nie zmniejszają one liczby wierszy, a jedynie dodają kontekst agregacji do każdego rekordu.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

funkcje agregujące sql
agregacja sql group by
funkcje agregujące sql przykłady
Autor Tymoteusz Kowalski
Tymoteusz Kowalski
Jestem Tymoteusz Kowalski, pasjonat technologii z wieloletnim doświadczeniem w analizowaniu i pisaniu na temat innowacji w branży. Od ponad pięciu lat zgłębiam różne aspekty technologiczne, koncentrując się na najnowszych trendach oraz ich wpływie na życie codzienne i biznes. Moje zainteresowania obejmują zarówno rozwój oprogramowania, jak i nowoczesne rozwiązania infrastrukturalne. Dzięki mojej pracy jako redaktor specjalistyczny, mam okazję przyglądać się z bliska dynamicznie zmieniającemu się rynkowi technologicznemu. Moim celem jest uproszczenie skomplikowanych danych i dostarczenie czytelnikom obiektywnej analizy, która pomoże im lepiej zrozumieć otaczający świat technologii. Zobowiązuję się do dostarczania rzetelnych, aktualnych i dokładnych informacji, które są niezbędne dla każdego, kto chce być na bieżąco z nowinkami technologicznymi. Wierzę, że wiedza powinna być dostępna dla wszystkich i staram się, aby moje publikacje były nie tylko informacyjne, ale także inspirujące.

Udostępnij artykuł

Napisz komentarz