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.
Każde zadanie ma jasno określony cel, a pod spodem znajdziesz zarówno podpowiedzi, jak i pełne rozwiązania. Dzięki temu możesz podejść do nich jak do mini–projektu — najpierw spróbuj sam, potem porównaj z gotowym zapytaniem i jego wynikiem.
To nie są akademickie przykłady, tylko sytuacje, które spotkasz w codziennej pracy z bazą: raportowanie sprzedaży, analiza klientów, czy filtrowanie danych po statusach i miesiącach. Jeśli dopiero zaczynasz, to najlepszy sposób, by zamienić teorię SQL w intuicję.
Jak pracować z bazą testową
Pracuj na stałym, małym datasetcie. Zawsze zaczynaj od obejrzenia schematu i kilku rekordów z każdej tabeli. Ustal wspólne klucze, kierunki relacji i kolumny do agregacji, aby Twoje zapytania SQL były przewidywalne i powtarzalne.
Ustal zasady filtrowania (np. uwzględniamy tylko zamówienia o statusie paid), dodawaj ORDER BY dla deterministycznych wyników i testuj warunki brzegowe (produkty bez sprzedaży, klienci bez zamówień). Jeśli dopiero zaczynasz, przypomnij sobie podstawy SQL: SELECT, WHERE, JOIN, GROUP BY: Podstawy SQL: SELECT, WHERE, JOIN, GROUP BY.
SQL1 2 3 4-- Szybkie sprawdzenie datasetu SET search_path TO shop; SELECT COUNT(*) AS liczba_klientow FROM customers; SELECT * FROM customers ORDER BY id LIMIT 5;

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ń.
Instrukcja uruchomienia datasetu
Poniżej jest kompletny skrypt dla PostgreSQL (zalecane: 14+). Uruchom go w psql lub w dowolnym kliencie SQL na świełej bazie. Skrypt tworzy schemat shop, tabele oraz przykładowe dane do ćwiczeń SQL.
Jeśli potrzebujesz pomocy w instalacji lub konfiguracji środowiska, zobacz: PostgreSQL: wprowadzenie, instalacja i pierwsza baza danych.
SQL1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86-- Schemat i tabele (PostgreSQL) CREATE SCHEMA IF NOT EXISTS shop; SET search_path TO shop; DROP TABLE IF EXISTS order_items; DROP TABLE IF EXISTS orders; DROP TABLE IF EXISTS products; DROP TABLE IF EXISTS customers; CREATE TABLE customers ( id INT PRIMARY KEY, first_name TEXT NOT NULL, last_name TEXT NOT NULL, city TEXT NOT NULL ); CREATE TABLE products ( id INT PRIMARY KEY, name TEXT NOT NULL, category TEXT NOT NULL, price NUMERIC(10,2) NOT NULL CHECK (price >= 0) ); CREATE TABLE orders ( id INT PRIMARY KEY, customer_id INT NOT NULL REFERENCES customers(id), order_date DATE NOT NULL, status TEXT NOT NULL CHECK (status IN ('new','paid','canceled')) ); CREATE TABLE order_items ( order_id INT NOT NULL REFERENCES orders(id) ON DELETE CASCADE, product_id INT NOT NULL REFERENCES products(id), quantity INT NOT NULL CHECK (quantity > 0), unit_price NUMERIC(10,2) NOT NULL CHECK (unit_price >= 0), PRIMARY KEY(order_id, product_id) ); -- Dane przykładowe INSERT INTO customers (id, first_name, last_name, city) VALUES (1,'Anna','Kowalska','Warszawa'), (2,'Jan','Nowak','Kraków'), (3,'Ola','Zielińska','Warszawa'), (4,'Piotr','Wiśniewski','Gdańsk'), (5,'Ewa','Kaczmarek','Wrocław'); INSERT INTO products (id, name, category, price) VALUES (1,'Laptop Pro','Elektronika',4500.00), (2,'Mysz bezprzewodowa','Elektronika',120.00), (3,'Książka SQL','Książki',80.00), (4,'Monitor 27"','Elektronika',900.00), (5,'Notes A5','Biuro',15.00), (6,'Długopis','Biuro',3.00); INSERT INTO orders (id, customer_id, order_date, status) VALUES (1,1,'2025-01-05','paid'), (2,2,'2025-01-20','paid'), (3,1,'2025-02-10','paid'), (4,3,'2025-02-15','canceled'), (5,4,'2025-03-02','paid'), (6,5,'2025-03-18','paid'), (7,2,'2025-03-28','paid'), (8,3,'2025-04-05','paid'); INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES -- Zamówienie 1 (1,1,1,4500.00), (1,2,1,120.00), -- Zamówienie 2 (2,3,2,80.00), (2,5,10,15.00), -- Zamówienie 3 (3,4,1,900.00), (3,2,2,120.00), -- Zamówienie 4 (canceled) (4,3,1,80.00), -- Zamówienie 5 (5,1,1,4300.00), (5,6,5,3.00), -- Zamówienie 6 (6,5,20,15.00), -- Zamówienie 7 (7,2,1,120.00), (7,4,1,880.00), -- Zamówienie 8 (8,3,1,75.00);
Zestaw 10 zadań z rosnącą trudnością
Poniższe zadania SQL są ułożone od prostych filtrów i sortowania, przez agregacje i JOIN, po HAVING, podzapytania i funkcje okna. Pracuj na schemacie shop. Jeśli utkniesz, zajrzyj niżej do podpowiedzi i rozwiązań.
- Klienci z Warszawy posortowani alfabetycznie po nazwisku, potem imieniu.
- Liczba klientów w każdym mieście, malejąco.
- Pięć najdroższych produktów.
- Wartość każdego zamówienia (suma quantity*unit_price).
- Łączna sprzedaż per klient, uwzględnij tylko paid.
- Klienci bez żadnego opłaconego zamówienia.
- Produkty sprzedane łącznie w liczbie > 5 sztuk (tylko paid).
- Miesięczna sprzedaż (YYYY-MM) i jej wartość (tylko paid).
- Dla każdego produktu: średnia cena sprzedaży vs aktualna cena.
- Top 3 kategorie wg przychodu oraz ich udział procentowy w całości.
Chcesz iść dalej po tych ćwiczeniach SQL? Zobacz: Zadania SQL: poziom średni.
Podpowiedzi i rozwiązania (do rozwinięcia)
Zanim spojrzysz w kod, spróbuj samodzielnie. Dla agregacji używaj GROUP BY i HAVING, dla “brak sprzedaży” – LEFT JOIN + IS NULL. Daty grupuj przez date_trunc('month', ...) lub formatowanie wynikowe to_char(..., 'YYYY-MM').
W MySQL odpowiednikiem date_trunc/to_char jest DATE_FORMAT, a składnia FILTER(...) nie występuje – użyj AVG(CASE WHEN ... THEN ... END). Jeśli potrzebujesz środowiska MySQL do porównań, zerknij: MySQL: wprowadzenie, instalacja i pierwsza baza danych.
SQL1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89-- T1: Klienci z Warszawy SELECT id, first_name, last_name FROM shop.customers WHERE city = 'Warszawa' ORDER BY last_name, first_name; -- T2: Liczba klientów per miasto SELECT city, COUNT(*) AS liczba_klientow FROM shop.customers GROUP BY city ORDER BY liczba_klientow DESC, city; -- T3: 5 najdroższych produktów SELECT id, name, price FROM shop.products ORDER BY price DESC, name LIMIT 5; -- T4: Wartość każdego zamówienia SELECT oi.order_id, SUM(oi.quantity * oi.unit_price) AS wartosc_zamowienia FROM shop.order_items oi GROUP BY oi.order_id ORDER BY oi.order_id; -- T5: Sprzedaż per klient (tylko paid) SELECT o.customer_id, c.first_name, c.last_name, SUM(oi.quantity * oi.unit_price) AS przychod FROM shop.orders o JOIN shop.order_items oi ON oi.order_id = o.id JOIN shop.customers c ON c.id = o.customer_id WHERE o.status = 'paid' GROUP BY o.customer_id, c.first_name, c.last_name ORDER BY przychod DESC; -- T6: Klienci bez opłaconych zamówień SELECT c.id, c.first_name, c.last_name FROM shop.customers c LEFT JOIN shop.orders o ON o.customer_id = c.id AND o.status = 'paid' WHERE o.id IS NULL ORDER BY c.id; -- T7: Produkty sprzedane > 5 sztuk (tylko paid) SELECT p.id, p.name, SUM(oi.quantity) AS sprzedane_sztuki FROM shop.products p JOIN shop.order_items oi ON oi.product_id = p.id JOIN shop.orders o ON o.id = oi.order_id WHERE o.status = 'paid' GROUP BY p.id, p.name HAVING SUM(oi.quantity) > 5 ORDER BY sprzedane_sztuki DESC, p.name; -- T8: Miesięczna sprzedaż (YYYY-MM) SELECT to_char(date_trunc('month', o.order_date), 'YYYY-MM') AS miesiac, SUM(oi.quantity * oi.unit_price) AS przychod FROM shop.orders o JOIN shop.order_items oi ON oi.order_id = o.id WHERE o.status = 'paid' GROUP BY date_trunc('month', o.order_date) ORDER BY 1 DESC; -- T9: Średnia cena sprzedaży vs aktualna cena (produkty bez sprzedaży też) SELECT p.id, p.name, p.price AS cena_aktualna, ROUND(AVG(oi.unit_price) FILTER (WHERE o.status = 'paid'), 2) AS sr_cena_sprzedazy FROM shop.products p LEFT JOIN shop.order_items oi ON oi.product_id = p.id LEFT JOIN shop.orders o ON o.id = oi.order_id GROUP BY p.id, p.name, p.price ORDER BY p.id; -- T10: Top 3 kategorie wg przychodu + udział % WITH sprzedaz AS ( SELECT p.category, SUM(oi.quantity * oi.unit_price) AS przychod FROM shop.products p JOIN shop.order_items oi ON oi.product_id = p.id JOIN shop.orders o ON o.id = oi.order_id WHERE o.status = 'paid' GROUP BY p.category ) SELECT category, przychod, ROUND(100.0 * przychod / SUM(przychod) OVER (), 2) AS udzial_proc FROM sprzedaz ORDER BY przychod DESC LIMIT 3;

MySQL — Jak zacząć? Darmowy e-book
Praktyczny przewodnik po świecie SQL. Poznaj typy danych, zapytania SELECT, JOIN, funkcje agregujące i nie tylko.
Jak samodzielnie analizować wynik zapytania
Zacznij od sanity check: czy liczba wierszy i sumy mają sens? Zweryfikuj wynik na pojedynczym obiekcie (np. dla jednego zamówienia policz ręcznie: 2×80 + 10×15 = 310). Dodawaj ORDER BY, żeby wynik był stabilny; sprawdzaj też wersję bez filtrów, a potem z filtrem (np. status = 'paid'), aby zobaczyć różnicę.
Szukaj duplikacji po JOIN: porównaj COUNT(*) z COUNT(DISTINCT ...) dla klucza głównego. Jeśli agregujesz, sprawdź kardynalność grupowania (czy wszystkie kolumny w SELECT są w GROUP BY albo w agregacji). Uważaj na NULL – COALESCE bywa konieczny, np. dla produktów bez sprzedaży.
Na koniec porównuj wyniki alternatywnymi zapytaniami SQL (np. z podzapytaniem zamiast JOIN), testuj przypadki brzegowe i dopisuj komentarze do zapytań. Dzięki temu ćwiczenia SQL stają się powtarzalne, a zapytania SQL czytelne i łatwe do weryfikacji.



