Podstawy SQL: SELECT, WHERE, JOIN, GROUP BY

Kacper Sieradziński
Kacper Sieradziński
19 sierpnia 2025SQL4 min czytania

SELECT, WHERE, JOIN i GROUP BY - to cztery filary SQL, bez których nie da się ruszyć dalej. To właśnie one pozwalają wybierać dane, filtrować je, łączyć tabele i tworzyć raporty. Jeśli opanujesz te konstrukcje, zrozumiesz 80% zapytań, które codziennie wykonują analitycy i programiści.

Obraz główny Podstawy SQL: SELECT, WHERE, JOIN, GROUP BY

Zapisz się na nasz newsletter

Otrzymuj regularne aktualizacje, specjalne oferty i porady od ekspertów, które pomogą Ci osiągnąć więcej w krótszym czasie.

W tym artykule poznasz składnię i logikę podstawowych poleceń SQL, nauczysz się łączyć tabele, grupować dane i filtrować wyniki. Zobaczysz też typowe błędy początkujących i dostaniesz zestaw krótkich zadań, które pozwolą Ci utrwalić wiedzę w praktyce.

Czym jest SQL i jak działa

SQL to język zapytań do relacyjnych baz danych. Umożliwia odczyt i modyfikację danych poprzez deklaratywne zapytania. Silnik bazy optymalizuje plan wykonania na podstawie statystyk i indeksów, a użytkownik opisuje wynik, nie algorytm.

Dane są zorganizowane w tabele (wiersze i kolumny), a relacje łączą wiersze poprzez klucze. Dokładny dobór typów ma znaczenie dla wydajności i poprawności porównań. Zobacz: Typy danych w SQL: porównanie PostgreSQL i MySQL.

SQL
1 2 3 4 -- Prosty odczyt danych SELECT id, name, email FROM customers WHERE active = true;

Składnia zapytań SELECT

SELECT definiuje kolumny wyniku, FROM wskazuje źródła, WHERE filtruje wiersze, GROUP BY agreguje, HAVING filtruje grupy, ORDER BY sortuje, a LIMIT ogranicza liczbę rekordów. Kolejność zapisu różni się od kolejności logicznego przetwarzania (FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT).

Używaj aliasów dla czytelności i deterministycznego sortowania. Funkcje agregujące (COUNT, SUM, AVG, MIN, MAX) wymagają spójnego GROUP BY dla kolumn nieagregowanych. Więcej złożonych wzorców (CTE, podzapytania) omawiam tutaj: Podzapytania i CTE w SQL.

SQL
1 2 3 4 5 6 7 8 9 -- Typowy szkielet zapytania SELECT c.id, c.name, SUM(o.total) AS revenue FROM customers c JOIN orders o ON o.customer_id = c.id WHERE o.status = 'paid' GROUP BY c.id, c.name HAVING SUM(o.total) > 1000 ORDER BY revenue DESC, c.id ASC LIMIT 10 OFFSET 0;
MySQL — Jak zacząć? Darmowy e-book

MySQL — Jak zacząć? Darmowy e-book

Praktyczny przewodnik po świecie SQL. Poznaj typy danych, zapytania SELECT, JOIN, funkcje agregujące i nie tylko.

Filtrowanie danych z użyciem WHERE

WHERE filtruje wiersze przed agregacją. Stosuj operatory =, <, >, BETWEEN, IN, LIKE/ILIKE oraz spójnie traktuj NULL (IS NULL, IS NOT NULL). Pamiętaj o priorytecie AND nad OR i używaj nawiasów, gdy łączysz warunki.

Filtrowanie korzysta z indeksów, jeśli predykaty są selektywne i „sargowalne” (np. kolumna = wartość). Wyrażenia na lewej stronie porównania i funkcje na kolumnie mogą wyłączyć indeks. Szczegóły: Indeksy w SQL: teoria i praktyka.

SQL
1 2 3 4 5 6 7 -- Przykłady predykatów WHERE SELECT id, name FROM customers WHERE (country = 'PL' AND active = true) OR (vip = true AND last_login >= '2025-01-01') AND email IS NOT NULL AND name ILIKE 'a%';

Łączenie tabel za pomocą JOIN

JOIN łączy wiersze z wielu tabel na podstawie warunku. Zawsze podawaj warunek w ON/USING, aby uniknąć iloczynu kartezjańskiego. Dobierz typ JOIN do oczekiwanego zachowania braków danych i kierunku „obowiązkowości”.

Wydajność JOIN zależy od indeksów na kolumnach kluczy i kardynalności. W trudnych przypadkach przeanalizuj plan wykonania: Optymalizacja zapytań SQL i analiza planów wykonania.

INNER JOIN

Zwraca tylko pary pasujących wierszy po obu stronach. Najczęstszy wariant dla „obowiązkowych” relacji.

SQL
1 2 3 4 SELECT o.id, c.name, o.total FROM orders o INNER JOIN customers c ON c.id = o.customer_id WHERE o.status = 'paid';

LEFT JOIN

Zwraca wszystkie wiersze z lewej tabeli, a z prawej dopasowane lub NULL, gdy brak dopasowania. Użyteczne do „opcjonalnych” relacji.

SQL
1 2 3 SELECT c.id, c.name, o.id AS order_id FROM customers c LEFT JOIN orders o ON o.customer_id = c.id AND o.status = 'paid';

RIGHT JOIN

Symetryczny do LEFT JOIN, ale mniej czytelny w praktyce. Często można go zastąpić LEFT JOIN przez zamianę ról tabel.

SQL
1 2 3 SELECT c.id, c.name, o.id AS order_id FROM orders o RIGHT JOIN customers c ON c.id = o.customer_id;

FULL JOIN

Zwraca wszystkie wiersze z obu tabel. Wiersze bez dopasowania mają NULL po przeciwnej stronie. Dostępny m.in. w PostgreSQL; MySQL nie wspiera FULL OUTER JOIN bez obejść.

SQL
1 2 3 4 -- PostgreSQL SELECT c.id AS customer_id, o.id AS order_id FROM customers c FULL JOIN orders o ON o.customer_id = c.id;

Grupowanie danych z GROUP BY i filtrowanie z HAVING

GROUP BY agreguje wiersze według kluczy grupy. Każda kolumna w SELECT musi być albo w GROUP BY, albo w funkcji agregującej. Klucze grupy determinują jeden wiersz w wyniku dla każdej grupy.

HAVING filtruje już zbudowane grupy, więc może odwoływać się do agregatów. WHERE działa przed agregacją, HAVING po. Używaj HAVING tylko dla warunków wymagających agregatów.

SQL
1 2 3 4 5 6 -- Suma przychodów per klient, tylko klienci z co najmniej 3 zamówieniami SELECT c.id, c.name, COUNT(o.id) AS orders_cnt, SUM(o.total) AS revenue FROM customers c JOIN orders o ON o.customer_id = c.id GROUP BY c.id, c.name HAVING COUNT(o.id) >= 3 AND SUM(o.total) > 2000;

Sortowanie i ograniczanie wyników z ORDER BY i LIMIT

ORDER BY definiuje kolejność wyników. Ustal kierunek (ASC/DESC) i, jeśli dostępne, politykę dla NULL (NULLS FIRST/LAST). Sortuj po stabilnych kolumnach, aby mieć deterministyczny wynik, zwłaszcza z LIMIT.

LIMIT ogranicza liczbę wierszy. OFFSET służy do paginacji, ale bywa kosztowny przy dużych wartościach. Preferuj „keyset pagination” (WHERE z warunkiem >/< na posortowanej kolumnie) dla dużych zbiorów.

SQL
1 2 3 4 5 6 7 -- Top 20 klientów według przychodu, stabilne sortowanie SELECT c.id, c.name, SUM(o.total) AS revenue FROM customers c JOIN orders o ON o.customer_id = c.id GROUP BY c.id, c.name ORDER BY revenue DESC, c.id ASC LIMIT 20;

Najczęstsze błędy początkujących

SELECT * w kodzie produkcyjnym. Prowadzi do nadmiaru danych i kruchych integracji. Zawsze wybieraj konkretne kolumny. Brak warunku JOIN lub warunek w WHERE zamiast ON przy zewnętrznych złączeniach. Skutkuje duplikacją lub utratą wierszy.

Porównania z NULL za pomocą =/<> zamiast IS NULL/IS NOT NULL. Łączenie AND/OR bez nawiasów i przypadkowe zmiany logiki. Użycie funkcji na kolumnie w WHERE, co uniemożliwia użycie indeksu i spowalnia zapytanie. Stosowanie OFFSET do głębokiej paginacji zamiast paginacji kluczowej.

Zadania do samodzielnego wykonania

  • Napisz SELECT, który wybiera id, name klientów z Polski, aktywnych w 2025 roku, posortowanych malejąco po dacie ostatniego logowania, ogranicz do 10 rekordów.
  • Zbuduj raport: suma total per klient z tabel orders, tylko status 'paid'. Pokaż klientów bez zamówień z wartością 0 (LEFT JOIN + COALESCE).
  • Policz liczbę zamówień i średnią wartość zamówienia per miesiąc (GROUP BY na wyekstrahowanym miesiącu), filtruj HAVING dla co najmniej 50 zamówień.
  • Znajdź 10 najczęściej kupowanych produktów wraz z liczbą unikalnych klientów (JOIN orders, order_items, products, GROUP BY product_id).
  • Zaimplementuj paginację kluczową po revenue: pobierz kolejną stronę wyników większych niż ostatni revenue/id z poprzedniej strony.

Więcej zadań i danych testowych: Zadania SQL: poziom podstawowy.

Kurs SkumajBazy — Czas w końcu nauczyć się SQLa

Kurs SkumajBazy — Czas w końcu nauczyć się SQLa

Kompleksowy kurs SQL dla programistów, analityków i wszystkich, którzy chcą efektywnie pracować z danymi. Od podstaw do zaawansowanych zapytań.