{<Z Kordian Zadrożny

AI, Strony WWW, Programowanie, Bazy danych

SQL od Zera, cz. 7: JOIN + GROUP BY oraz twój pierwszy krok w podzapytania.

utworzone przez | lis 5, 2025 | Kurs SQL | 0 komentarzy

Cześć! Witam Cię w siódmej lekcji kursu „SQL od Zera”.

W poprzedniej lekcji nauczyliśmy się agregować dane. Potrafimy już zliczać (COUNT), sumować (SUM) i grupować (GROUP BY). Używaliśmy nawet HAVING do filtrowania grup i JOIN do łączenia tabel, aby nasze raporty miały sens (np. pokazywały nazwy miast, a nie tylko ID klienta).

Dzisiejszy plan jest ambitny. Zaczniemy od rozwiązania zadania domowego z Lekcji 6, aby utrwalić wiedzę. Następnie, zgodnie z Twoją prośbą, raz na zawsze wyjaśnimy różnicę między WHERE i HAVING.

A potem… dodamy „coś trudniejszego”. Pokażę Ci, jak grupować dane po wielu kolumnach jednocześnie (np. sprzedaż na miasto i na kategorię) oraz jak pisać zapytania wewnątrz zapytań (czyli tzw. podzapytania lub subqueries). Zaczynamy!

Rozwiązanie Zadania Domowego (z Lekcji 6)

Czołem! Zanim przejdziemy do „czegoś trudniejszego”, sprawdźmy zadania domowe z ostatniej lekcji. Zobacz, czy Twoje zapytania dały podobne wyniki.

Zadanie 1: Pokaż klientów (ID lub lepiej imię i nazwisko), którzy wydali w sumie ponad 200 zł.

Rozwiązanie:

To było klasyczne zadanie na GROUP BY połączone z filtrowaniem grup za pomocą HAVING.

SQL

SELECT 
    k.imie || ' ' || k.nazwisko AS klient, 
    SUM(z.wartosc_calkowita) AS laczna_wartosc
FROM zamowienia z
JOIN klienci k ON z.klient_id = k.klient_id
GROUP BY klient
HAVING SUM(z.wartosc_calkowita) > 200
ORDER BY laczna_wartosc DESC;

Użyliśmy JOIN, aby dobrać się do imienia i nazwiska, GROUP BY po naszym nowym aliasie klient, a na końcu HAVING, aby odfiltrować tylko tych, których SUM przekroczył 200.

Zadanie 2: Pokaż miasta, z których mamy WIĘCEJ NIŻ jednego klienta.

Rozwiązanie:

Tu agregowaliśmy dane z tabeli klienci. Kluczem znowu było HAVING, ponieważ filtrowaliśmy po zliczeniu (COUNT).

SQL

SELECT 
    miasto, 
    COUNT(*) AS liczba_klientow
FROM klienci
GROUP BY miasto
HAVING COUNT(*) > 1;

Zadanie 3: Pokaż kwotę maksymalnego zamówienia dla każdego miasta.

Rozwiązanie:

To zadanie wymagało połączenia tabel, pogrupowania ich po mieście i użycia funkcji agregującej MAX().

SQL

SELECT 
    k.miasto, 
    MAX(z.wartosc_calkowita) AS max_zamowienie
FROM zamowienia z
JOIN klienci k ON z.klient_id = k.klient_id
GROUP BY k.miasto
ORDER BY max_zamowienie DESC;

Doskonale. Skoro mamy to z głowy, wróćmy do naszego planu na Lekcję 7: musimy ostatecznie utrwalić różnicę między WHERE i HAVING oraz dodać „coś trudniejszego”.

Zajmijmy się tym.

Ostateczne starcie: WHERE vs. HAVING

W Lekcji 6 wspomnieliśmy o różnicy, ale teraz ją ugruntujemy. Oba służą do filtrowania, ale działają na zupełnie innych etapach.

  • WHERE – Filtruje przed grupowaniem.
  • HAVING – Filtruje po grupowaniu.

Pomyśl o tym jak o przygotowywaniu wielkiej kolacji w restauracji:

WHERE to selekcja składników. Kucharz mówi: „OK, do zrobienia sałatek wezmę TYLKO świeże pomidory (WHERE warzywo = 'pomidor’ AND stan = 'świeży’)”.

Reszta składników (np. zwiędła sałata) w ogóle nie trafia na stół.

HAVING to selekcja gotowych dań. Kucharz przygotował już wszystkie sałatki (zrobił GROUP BY). Teraz menedżer mówi: „Na salę wydajemy TYLKO te sałatki, których waga (SUM(waga_skladnikow)) przekracza 200 gramów (HAVING SUM(waga_skladnikow) > 200)”.

Mówiąc językiem SQL:

KlauzulaKiedy działa?Na czym operuje?Przykład
WHEREPrzed GROUP BY (przed agregacją)Na pojedynczych, surowych wierszachWHERE k.miasto = 'Warszawa'
HAVINGPo GROUP BY (po agregacji)Na całych „grupach” wierszyHAVING COUNT(*) > 2

Przykład „Turbo” (łączący oba):

Chcemy zobaczyć łączną sprzedaż dla klientów TYLKO z Warszawy (WHERE), ale pokazać tylko tych, których łączna suma zamówień przekroczyła 50 zł (HAVING).

SQL

SELECT 
    k.imie || ' ' || k.nazwisko AS klient, 
    SUM(z.wartosc_calkowita) AS laczna_wartosc
FROM zamowienia z
JOIN klienci k ON z.klient_id = k.klient_id
WHERE k.miasto = 'Warszawa'  -- 1. Filtrujemy wiersze PRZED grupowaniem
GROUP BY klient
HAVING SUM(z.wartosc_calkowita) > 50; -- 2. Filtrujemy GRUPY PO grupowaniu

Teraz mam nadzieję ta różnica powinna być już dla Ciebie absolutnie oczywista.

Coś trudniejszego (cz. 1) – Grupowanie po wielu kolumnach

Do tej pory grupowaliśmy po jednej kolumnie (np. miasto albo status). Ale co, jeśli chcemy stworzyć bardziej szczegółowy raport?

Na przykład: Policz, ile zamówień w każdym statusie pochodzi z każdego miasta.

Nic prostszego! Wystarczy dodać obie kolumny do GROUP BY:

SQL

SELECT 
    k.miasto, 
    z.status, 
    COUNT(*) AS liczba_zamowien
FROM zamowienia z
JOIN klienci k ON z.klient_id = k.klient_id
GROUP BY k.miasto, z.status   -- <--- Kluczowa zmiana!
ORDER BY k.miasto, z.status;

Co tu się stało?

Baza danych stworzyła teraz grupy dla każdej unikalnej kombinacji miasta i statusu (np. „Kraków – Nowe”, „Kraków – Wysłane”, „Warszawa – Nowe”, „Warszawa – Wysłane” itd.) i policzyła rekordy dla każdej z tych mikro-grup.


Coś trudniejszego (cz. 2) – Liczenie unikalnych wartości COUNT(DISTINCT ...)

Pojawiło się ciekawe pytanie biznesowe: W każdym miesiącu mieliśmy X zamówień. Ale ilu różnych klientów je złożyło?

Spójrzmy na dane:

Klient Jan (ID: 1) złożył 3 zamówienia.

Klient Anna (ID: 2) złożyła 2 zamówienia.

Jeśli zrobimy COUNT(klient_id), wynik będzie 5 (bo liczy wiersze). Ale my chcemy wiedzieć, że w tym zbiorze było tylko 2 unikalnych klientów. Do tego służy COUNT(DISTINCT nazwa_kolumny).

Przykład: Policzmy liczbę wszystkich zamówień ORAZ liczbę unikalnych klientów w każdym miesiącu.

SQL

SELECT 
    strftime('%Y-%m', data_zlozenia) AS miesiac,
    COUNT(*) AS liczba_wszystkich_zamowien,
    COUNT(DISTINCT klient_id) AS liczba_unikalnych_klientow
FROM zamowienia
GROUP BY miesiac
ORDER BY miesiac;

Wynik pokaże Ci np., że w 2024-09 mieliśmy 15 zamówień, ale złożyło je tylko 14 unikalnych klientów (co oznacza, że niektórzy kupowali więcej niż raz).

„Coś trudniejszego” (cz. 3) – Podzapytania (Subqueries) -podstawy

Zapnij pasy. Wchodzimy poziom głębiej.

Podzapytanie (Subquery) to po prostu zapytanie SELECT umieszczone wewnątrz innego zapytania. To jak film „Incepcja” w SQL.

Pozwalają one rozwiązywać złożone, wieloetapowe problemy w jednym poleceniu.

Problem do rozwiązania:

Chcę wyświetlić listę książek, których cena jest wyższa niż średnia cena WSZYSTKICH książek w księgarni.

Jak byśmy to zrobili do tej pory?

  1. Najpierw policzylibyśmy średnią:SELECT AVG(cena) FROM ksiazki; (Wynik to np. 45.42)
  2. Potem napisalibyśmy drugie zapytanie, wklepując tę liczbę ręcznie:SELECT tytul, cena FROM ksiazki WHERE cena > 45.42;

To słabe. A co, jeśli jutro dodamy nową książkę i średnia się zmieni?

Rozwiązanie z podzapytaniem:

Umieśćmy pierwsze zapytanie (w nawiasach) bezpośrednio w klauzuli WHERE drugiego zapytania!

SQL

SELECT tytul, cena
FROM ksiazki
WHERE cena > (
    SELECT AVG(cena) 
    FROM ksiazki
);

Jak to działa?

Baza danych najpierw wykona zapytanie wewnętrzne (SELECT AVG(cena) FROM ksiazki), ustali, że wynik to 45.42, a następnie „podstawi” tę wartość do zapytania zewnętrznego. Całość zadzieje się automatycznie.

To tylko przedsmak możliwości podzapytań, ale już teraz otwiera Ci to zupełnie nowe możliwości analityczne!

To podzapytanie wykona się dla każdego rekordu. Czy w tym przypadku 19 razy! A teraz wyobraź sobie, że masz bazę z milionem rekordów. Trzeba mieć to na uwadze, w kolejnych lekcjach pokażę inne możliwości zapytań, CTE, ale często trzeba użyć wszystkich opcji.

Zadanie Domowe (Lekcja 7)

Czas poćwiczyć nowe umiejętności!

  1. (Grupowanie po wielu kolumnach): Policz, ile książek (COUNT) każdy autor (autor_id) napisał dla każdego wydawnictwa (wydawnictwo_id). Chcemy zobaczyć, który autor najwięcej współpracował z którym wydawcą.
  2. (Podzapytanie w WHERE): Znajdź wszystkich klientów (klienci), którzy mieszkają w tym samym mieście co 'Anna Nowak’, ale nie są Anną Nowak. (Wskazówka: ...WHERE miasto = (SELECT miasto FROM klienci WHERE imie = 'Anna' AND nazwisko = 'Nowak') AND imie != 'Anna').
  3. (JOIN + HAVING): Pokaż nazwy kategorii (kategorie.nazwa) oraz średnią cenę książek w tych kategoriach, ale wyświetl tylko te kategorie, w których średnia cena (AVG(k.cena)) jest niższa niż 45 zł.

Powodzenia!

0 komentarzy

Wyślij komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Share This

Share this post with your friends!