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.
SQL1 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.
SQL1 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
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.
SQL1 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.
SQL1 2 3 4SELECT 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.
SQL1 2 3SELECT 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.
SQL1 2 3SELECT 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ść.
SQL1 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.
SQL1 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.
SQL1 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
Kompleksowy kurs SQL dla programistów, analityków i wszystkich, którzy chcą efektywnie pracować z danymi. Od podstaw do zaawansowanych zapytań.



