Strona główna Bazy danych sklep_online — ćwiczenia SQL
INF.03 · XAMPP · klasa 4

SQL – sklep
internetowy

18 ćwiczeń na bazie sklepu z elektroniką. Od prostych zapytań SELECT po transakcje z ROLLBACK, widoki (VIEW) i zaawansowane JOIN-y.

SELECT JOIN GROUP BY HAVING INSERT UPDATE DELETE VIEW TRANSACTION COUNT / AVG / SUM COALESCE
18Ćwiczeń
5Tabel
3Poziomy trudności

Baza danych sklep_online

MySQL / MariaDB · XAMPP
klienci
id_klientaINT AIPK
imieVARCHAR(50)
nazwiskoVARCHAR(80)
emailVARCHAR(120)
miastoVARCHAR(60)
data_rejDATE
produkty
id_produktuINT AIPK
nazwaVARCHAR(100)
cenaDECIMAL(10,2)
id_kategoriiINTFK
stan_magINT
producentVARCHAR(60)
zamowienia
id_zamowieniaINT AIPK
id_klientaINTFK
data_zamDATETIME
statusENUM
wartosc_totalDECIMAL(10,2)
pozycje_zam
id_pozycjiINT AIPK
id_zamowieniaINTFK
id_produktuINTFK
iloscINT
cena_jednDECIMAL(10,2)
kategorie
id_kategoriiINT AIPK
nazwa_katVARCHAR(60)
opisTEXT
Relacje między tabelami:
klienci.id_klienta zamowienia.id_klienta  (1:N)
kategorie.id_kategorii produkty.id_kategorii  (1:N)
zamowienia.id_zamowienia pozycje_zam.id_zamowienia  (1:N)
produkty.id_produktu pozycje_zam.id_produktu  (1:N)
-- Wklej w phpMyAdmin → zakładka SQL
CREATE DATABASE sklep_online
  CHARACTER SET utf8mb4 COLLATE utf8mb4_polish_ci;
USE sklep_online;

CREATE TABLE kategorie (
  id_kategorii  INT AUTO_INCREMENT PRIMARY KEY,
  nazwa_kat     VARCHAR(60) NOT NULL,
  opis          TEXT
);

CREATE TABLE klienci (
  id_klienta  INT AUTO_INCREMENT PRIMARY KEY,
  imie        VARCHAR(50)  NOT NULL,
  nazwisko    VARCHAR(80)  NOT NULL,
  email       VARCHAR(120) UNIQUE NOT NULL,
  miasto      VARCHAR(60),
  data_rej    DATE DEFAULT (CURDATE())
);

CREATE TABLE produkty (
  id_produktu  INT AUTO_INCREMENT PRIMARY KEY,
  nazwa        VARCHAR(100) NOT NULL,
  cena         DECIMAL(10,2) NOT NULL,
  id_kategorii INT,
  stan_mag     INT DEFAULT 0,
  producent    VARCHAR(60),
  FOREIGN KEY (id_kategorii) REFERENCES kategorie(id_kategorii)
);

CREATE TABLE zamowienia (
  id_zamowienia  INT AUTO_INCREMENT PRIMARY KEY,
  id_klienta     INT NOT NULL,
  data_zam       DATETIME DEFAULT (NOW()),
  status         ENUM('nowe','w_realizacji','wysłane','dostarczone','anulowane')
                 DEFAULT 'nowe',
  wartosc_total  DECIMAL(10,2),
  FOREIGN KEY (id_klienta) REFERENCES klienci(id_klienta)
);

CREATE TABLE pozycje_zam (
  id_pozycji    INT AUTO_INCREMENT PRIMARY KEY,
  id_zamowienia INT NOT NULL,
  id_produktu   INT NOT NULL,
  ilosc         INT DEFAULT 1,
  cena_jedn     DECIMAL(10,2) NOT NULL,
  FOREIGN KEY (id_zamowienia) REFERENCES zamowienia(id_zamowienia),
  FOREIGN KEY (id_produktu)   REFERENCES produkty(id_produktu)
);
INSERT INTO kategorie (nazwa_kat, opis) VALUES
  ('Smartfony',  'Telefony komórkowe i akcesoria'),
  ('Laptopy',    'Komputery przenośne'),
  ('Akcesoria',  'Kable, ładowarki, etui'),
  ('Tablety',    'Tablety i e-czytniki'),
  ('Audio',      'Słuchawki, głośniki');

INSERT INTO klienci (imie, nazwisko, email, miasto, data_rej) VALUES
  ('Anna',    'Kowalska',    'anna.k@email.pl',    'Warszawa', '2023-01-15'),
  ('Piotr',   'Nowak',        'p.nowak@email.pl',   'Kraków',   '2023-03-22'),
  ('Maria',   'Wiśniewska',   'm.wisn@email.pl',    'Gdańsk',   '2023-05-10'),
  ('Tomasz',  'Wójcik',       't.wojcik@email.pl',  'Wrocław',  '2023-07-08'),
  ('Katarzyna','Lewandowska',  'kat.l@email.pl',    'Warszawa', '2023-09-30'),
  ('Marek',   'Zieliński',    'm.ziel@email.pl',   'Łódź',     '2024-01-12'),
  ('Ewa',     'Szymańska',    'e.szym@email.pl',   'Poznań',   '2024-02-20');

INSERT INTO produkty (nazwa, cena, id_kategorii, stan_mag, producent) VALUES
  ('iPhone 15 128GB',      3999.00, 1, 25, 'Apple'),
  ('Samsung Galaxy S24',   3499.00, 1, 30, 'Samsung'),
  ('Xiaomi Redmi Note 13', 899.00,  1, 50, 'Xiaomi'),
  ('MacBook Air M2',       6499.00, 2, 10, 'Apple'),
  ('Lenovo IdeaPad 3',     2299.00, 2, 15, 'Lenovo'),
  ('HP Pavilion 15',       2799.00, 2, 8,  'HP'),
  ('Kabel USB-C 2m',       29.99,   3, 200,'Baseus'),
  ('Etui iPhone 15',       49.99,   3, 80, 'Spigen'),
  ('iPad Air 11"',         4299.00, 4, 12, 'Apple'),
  ('Sony WH-1000XM5',     1399.00, 5, 20, 'Sony'),
  ('JBL Clip 4',           199.00,  5, 35, 'JBL'),
  ('Powerbank 20000mAh',   149.00,  3, 45, 'Xiaomi');

INSERT INTO zamowienia (id_klienta, data_zam, status, wartosc_total) VALUES
  (1,'2024-01-10 10:30:00','dostarczone',4048.99),
  (2,'2024-01-15 14:20:00','dostarczone',3499.00),
  (3,'2024-02-01 09:00:00','wysłane',    2299.00),
  (1,'2024-02-20 16:45:00','dostarczone',1399.00),
  (4,'2024-03-05 11:10:00','w_realizacji',6499.00),
  (5,'2024-03-12 13:30:00','nowe',       448.99),
  (2,'2024-03-18 08:00:00','anulowane',  4299.00),
  (6,'2024-04-02 17:00:00','dostarczone',199.00);

INSERT INTO pozycje_zam (id_zamowienia, id_produktu, ilosc, cena_jedn) VALUES
  (1,1,1,3999.00),(1,8,1,49.99),
  (2,2,1,3499.00),
  (3,5,1,2299.00),
  (4,10,1,1399.00),
  (5,4,1,6499.00),
  (6,7,3,29.99),(6,12,2,149.00),(6,8,1,49.99),
  (7,9,1,4299.00),
  (8,11,1,199.00);

Ćwiczenia

18 zadań · kliknij aby rozwinąć
Twój postęp
0 / 18
Filtruj:
Łatwe — podstawy SELECT, INSERT, UPDATE 6 ćwiczeń
01
łatwe Wyświetl wszystkich klientów

Wyświetl imię, nazwisko, email i miasto wszystkich klientów, posortowanych alfabetycznie po nazwisku.

SELECT imie, nazwisko, email, miasto
FROM   klienci
ORDER BY nazwisko ASC;
imienazwiskoemailmiasto
AnnaKowalskaanna.k@email.plWarszawa
MariaWiśniewskam.wisn@email.plGdańsk
TomaszWójcikt.wojcik@email.plWrocław
… (7 wierszy łącznie)
Wynik: 7 wierszy
02
łatwe Produkty tańsze niż 500 zł

Wyświetl nazwę, cenę i producenta wszystkich produktów tańszych niż 500 zł. Posortuj od najtańszego.

SELECT nazwa, cena, producent
FROM   produkty
WHERE  cena < 500.00
ORDER BY cena ASC;
nazwacenaproducent
Kabel USB-C 2m29.99Baseus
Etui iPhone 1549.99Spigen
Powerbank 20000mAh149.00Xiaomi
JBL Clip 4199.00JBL
Wynik: 4 wiersze
03
łatwe COUNT — liczba klientów z Warszawy

Zlicz ilu klientów pochodzi z Warszawy. Użyj funkcji agregującej COUNT.

SELECT COUNT(*) AS liczba_klientow
FROM   klienci
WHERE  miasto = 'Warszawa';
liczba_klientow
2
Wynik: 1 wiersz
04
łatwe Produkty Apple

Wyświetl wszystkie produkty producenta Apple. Pokaż nazwę, cenę i stan magazynowy. Posortuj malejąco po cenie.

SELECT nazwa, cena, stan_mag
FROM   produkty
WHERE  producent = 'Apple'
ORDER BY cena DESC;
nazwacenastan_mag
MacBook Air M26499.0010
iPad Air 11"4299.0012
iPhone 15 128GB3999.0025
Wynik: 3 wiersze
05
łatwe UPDATE — zmień status zamówienia

Zmień status zamówienia nr 6 z 'nowe' na 'w_realizacji'. Następnie sprawdź wynik SELECT-em.

UPDATE zamowienia
SET    status = 'w_realizacji'
WHERE  id_zamowienia = 6;

-- Sprawdzenie:
SELECT id_zamowienia, status, data_zam
FROM   zamowienia
WHERE  id_zamowienia = 6;
⚠️ Pamiętaj o WHERE przy UPDATE! Bez niego zmienisz status wszystkich zamówień w tabeli. Zawsze najpierw sprawdź SELECT-em, które rekordy zostaną zmienione.
06
łatwe INSERT — dodaj nowy produkt

Dodaj nowy produkt: Sony WF-1000XM4, cena 799 zł, kategoria Audio (id=5), stan magazynowy 15, producent Sony.

INSERT INTO produkty (nazwa, cena, id_kategorii, stan_mag, producent)
VALUES ('Sony WF-1000XM4', 799.00, 5, 15, 'Sony');

-- Sprawdzenie:
SELECT * FROM produkty WHERE producent = 'Sony';
Średnie — JOIN, GROUP BY, HAVING, LIKE 8 ćwiczeń
07
średnie JOIN — produkty z nazwą kategorii

Wyświetl nazwę produktu, cenę i nazwę kategorii. Użyj INNER JOIN. Posortuj po kategorii i cenie.

SELECT
  p.nazwa,
  p.cena,
  k.nazwa_kat
FROM       produkty   p
INNER JOIN kategorie  k ON p.id_kategorii = k.id_kategorii
ORDER BY   k.nazwa_kat, p.cena;
nazwacenanazwa_kat
Kabel USB-C 2m29.99Akcesoria
Etui iPhone 1549.99Akcesoria
JBL Clip 4199.00Audio
… (12 wierszy)
Wynik: 12 wierszy
Aliasy tabel (p, k) skracają zapis. ON wskazuje które kolumny łączą tabele — zawsze klucz główny = klucz obcy. Przy INNER JOIN wiersze bez pasującego rekordu w drugiej tabeli nie pojawią się w wynikach.
08
średnie GROUP BY — klienci wg miast

Policz ilu klientów pochodzi z każdego miasta. Posortuj malejąco wg liczby klientów.

SELECT
  miasto,
  COUNT(*) AS liczba_klientow
FROM     klienci
GROUP BY miasto
ORDER BY liczba_klientow DESC;
miastoliczba_klientow
Warszawa2
Kraków1
Gdańsk1
… (6 miast)
Wynik: 6 wierszy
09
średnie MAX, MIN, AVG — statystyki cen wg kategorii

Oblicz dla każdej kategorii najwyższą, najniższą i średnią cenę produktów. Pokaż tylko kategorie, gdzie średnia cena przekracza 500 zł.

SELECT
  k.nazwa_kat,
  MAX(p.cena)              AS cena_max,
  MIN(p.cena)              AS cena_min,
  ROUND(AVG(p.cena), 2)  AS srednia
FROM       produkty   p
INNER JOIN kategorie  k ON p.id_kategorii = k.id_kategorii
GROUP BY   k.id_kategorii, k.nazwa_kat
HAVING     AVG(p.cena) > 500
ORDER BY   srednia DESC;
nazwa_katcena_maxcena_minsrednia
Tablety4299.004299.004299.00
Laptopy6499.002299.003865.67
Smartfony3999.00899.002799.00
Audio1399.00199.00799.00
Wynik: 4 wiersze (Akcesoria odfiltrowana — śr. poniżej 500 zł)
WHERE filtruje wiersze przed grupowaniem. HAVING filtruje grupy po zgrupowaniu. Nigdy nie używaj HAVING do filtrowania zwykłych kolumn — do tego służy WHERE.
10
średnie Historia zamówień klienta

Wyświetl wszystkie zamówienia Anny Kowalskiej — numer, datę, status i wartość całkowitą.

SELECT
  z.id_zamowienia,
  z.data_zam,
  z.status,
  z.wartosc_total
FROM       zamowienia  z
INNER JOIN klienci     k ON z.id_klienta = k.id_klienta
WHERE      k.imie = 'Anna' AND k.nazwisko = 'Kowalska'
ORDER BY   z.data_zam;
id_zamowieniadata_zamstatuswartosc_total
12024-01-10 10:30:00dostarczone4048.99
42024-02-20 16:45:00dostarczone1399.00
Wynik: 2 wiersze
11
średnie LIKE — wyszukiwanie po nazwie

Znajdź wszystkie produkty, których nazwa zawiera słowo 'Sony'. Użyj operatora LIKE.

SELECT id_produktu, nazwa, cena, producent
FROM   produkty
WHERE  nazwa LIKE '%Sony%'
ORDER BY cena DESC;
id_produktunazwacenaproducent
10Sony WH-1000XM51399.00Sony
Wynik: 1 wiersz
% zastępuje dowolny ciąg znaków. '%Sony%' = zawiera "Sony" gdziekolwiek. 'Sony%' = zaczyna się na "Sony". '%XM5' = kończy się na "XM5". Podkreślnik _ zastępuje dokładnie jeden znak.
12
średnie UPDATE — podwyżka cen o 10%

Zwiększ ceny wszystkich produktów w kategorii Akcesoria (id=3) o 10%.

-- Przed zmianą:
SELECT nazwa, cena FROM produkty WHERE id_kategorii = 3;

UPDATE produkty
SET    cena = cena * 1.10
WHERE  id_kategorii = 3;

-- Po zmianie:
SELECT nazwa, cena FROM produkty WHERE id_kategorii = 3;
cena * 1.10 to mnożenie aktualnej wartości przez 1.10 (czyli +10%). Możesz też napisać SET cena = cena + cena * 0.10. Wyniki mogą się nieznacznie różnić przez zaokrąglenia DECIMAL.
13
średnie JOIN trójstronny — szczegóły zamówienia

Wyświetl szczegóły zamówienia nr 6: imię i nazwisko klienta, nazwy produktów, ilości i ceny jednostkowe — wylicz też wartość każdej pozycji.

SELECT
  CONCAT(kl.imie, ' ', kl.nazwisko) AS klient,
  pr.nazwa                           AS produkt,
  pz.ilosc,
  pz.cena_jedn,
  pz.ilosc * pz.cena_jedn            AS wartosc
FROM       pozycje_zam pz
INNER JOIN zamowienia  z  ON pz.id_zamowienia = z.id_zamowienia
INNER JOIN klienci     kl ON z.id_klienta    = kl.id_klienta
INNER JOIN produkty    pr ON pz.id_produktu   = pr.id_produktu
WHERE      pz.id_zamowienia = 6;
klientproduktilosccena_jednwartosc
Katarzyna LewandowskaKabel USB-C 2m329.9989.97
Katarzyna LewandowskaPowerbank 20000mAh2149.00298.00
Katarzyna LewandowskaEtui iPhone 15149.9949.99
Wynik: 3 wiersze — 3 produkty w zamówieniu nr 6
14
średnie DELETE — usuń anulowane zamówienia

Usuń z bazy wszystkie pozycje zamówień należące do zamówień anulowanych, a następnie usuń same zamówienia anulowane.

-- Krok 1: usuń pozycje anulowanych zamówień (klucz obcy!)
DELETE FROM pozycje_zam
WHERE       id_zamowienia IN (
  SELECT id_zamowienia
  FROM   zamowienia
  WHERE  status = 'anulowane'
);

-- Krok 2: usuń zamówienia anulowane
DELETE FROM zamowienia
WHERE       status = 'anulowane';
Kolejność DELETE ma znaczenie! Najpierw usuń dane z tabeli podrzędnej (pozycje_zam), potem z nadrzędnej (zamowienia). Inaczej MySQL zablokuje operację z powodu naruszenia klucza obcego. Przed DELETE zawsze zrób backup lub użyj transakcji.
Trudne — LEFT JOIN, subquery, VIEW, TRANSACTION 4 ćwiczenia
15
trudne LEFT JOIN — ranking klientów wg wartości zakupów

Stwórz ranking klientów wg łącznej wartości dostarczonych zamówień. Pokaż też klientów bez zamówień (wartość 0).

SELECT
  CONCAT(k.imie, ' ', k.nazwisko)    AS klient,
  COUNT(z.id_zamowienia)             AS liczba_zam,
  COALESCE(SUM(z.wartosc_total), 0) AS laczna_wartosc
FROM      klienci     k
LEFT JOIN zamowienia  z
  ON  k.id_klienta = z.id_klienta
  AND z.status = 'dostarczone'
GROUP BY  k.id_klienta, k.imie, k.nazwisko
ORDER BY  laczna_wartosc DESC;
klientliczba_zamlaczna_wartosc
Anna Kowalska25447.99
Piotr Nowak13499.00
Marek Zieliński1199.00
Maria Wiśniewska00
… (7 klientów)
Wynik: 7 wierszy — wszyscy klienci, nawet bez zamówień
LEFT JOIN zwraca wszystkich klientów, nawet bez pasujących zamówień (wtedy NULL). Warunek AND z.status = 'dostarczone' jest w ON, nie w WHERE — gdyby był w WHERE, wykluczyłby klientów bez zamówień. COALESCE(SUM(...), 0) zamienia NULL na 0.
16
trudne Podzapytanie (subquery) — produkty powyżej średniej

Wyświetl produkty, których cena jest wyższa niż średnia cena wszystkich produktów. Użyj podzapytania zagnieżdżonego.

-- Podgląd średniej:
SELECT ROUND(AVG(cena), 2) AS srednia_cena FROM produkty;

-- Produkty powyżej średniej:
SELECT nazwa, cena, producent
FROM   produkty
WHERE  cena > (
  SELECT AVG(cena) FROM produkty
)
ORDER BY cena DESC;
nazwacenaproducent
MacBook Air M26499.00Apple
iPad Air 11"4299.00Apple
iPhone 15 128GB3999.00Apple
Samsung Galaxy S243499.00Samsung
HP Pavilion 152799.00HP
Średnia: ~2151 zł · Wynik: 5 produktów powyżej średniej
Podzapytanie (subquery) wykonuje się jako pierwsze i zwraca jedną wartość (AVG), której używa zewnętrzne zapytanie. Podzapytania można zagnieżdżać wielokrotnie, choć zbyt głębokie zagnieżdżenie szkodzi czytelności — wtedy lepiej użyć CTE (WITH).
17
trudne CREATE VIEW — podsumowanie sprzedaży

Utwórz widok v_sprzedaz_produktow, który pokazuje łączną sprzedaną ilość i przychód dla każdego produktu. Następnie użyj go jak zwykłej tabeli.

CREATE VIEW v_sprzedaz_produktow AS
SELECT
  pr.id_produktu,
  pr.nazwa,
  pr.producent,
  COALESCE(SUM(pz.ilosc), 0)               AS sprzedana_ilosc,
  COALESCE(SUM(pz.ilosc * pz.cena_jedn), 0) AS przychod
FROM      produkty    pr
LEFT JOIN pozycje_zam pz ON pr.id_produktu = pz.id_produktu
GROUP BY  pr.id_produktu, pr.nazwa, pr.producent;

-- Użycie widoku — TOP 5 produktów wg przychodu:
SELECT * FROM v_sprzedaz_produktow
ORDER BY przychod DESC
LIMIT 5;

-- Usuwanie widoku:
-- DROP VIEW v_sprzedaz_produktow;
COALESCE(wartość, 0) zamienia NULL na 0 — przydatne gdy produkt nie był jeszcze sprzedany (LEFT JOIN zwraca NULL). Widok działa jak wirtualna tabela — możesz go odpytywać SELECT-em, filtrować WHERE, łączyć JOIN-em. Nie przechowuje danych, tylko definicję zapytania.
18
trudne TRANSACTION — bezpieczne dodanie zamówienia

Napisz transakcję, która bezpiecznie doda nowe zamówienie wraz z pozycjami i zmniejszy stany magazynowe. Jeśli coś się nie powiedzie — cofnij całą operację.

START TRANSACTION;

-- 1. Dodaj zamówienie
INSERT INTO zamowienia (id_klienta, status, wartosc_total)
VALUES (3, 'nowe', 4198.00);

-- 2. Pobierz ID nowego zamówienia
SET @nowe_id = LAST_INSERT_ID();

-- 3. Dodaj pozycje zamówienia
INSERT INTO pozycje_zam (id_zamowienia, id_produktu, ilosc, cena_jedn) VALUES
  (@nowe_id, 1, 1, 3999.00),
  (@nowe_id, 7, 3, 29.99),
  (@nowe_id, 8, 2, 49.99);

-- 4. Zmniejsz stany magazynowe
UPDATE produkty SET stan_mag = stan_mag - 1 WHERE id_produktu = 1;
UPDATE produkty SET stan_mag = stan_mag - 3 WHERE id_produktu = 7;
UPDATE produkty SET stan_mag = stan_mag - 2 WHERE id_produktu = 8;

-- 5. Zatwierdź (lub cofnij w razie błędu)
COMMIT;
-- ROLLBACK;  ← cofnięcie wszystkich kroków
Transakcja to zestaw operacji, które albo WSZYSTKIE się wykonają (COMMIT), albo żadna (ROLLBACK). Zasada ACID: Atomicity, Consistency, Isolation, Durability. Używaj transakcji przy każdej operacji modyfikującej kilka powiązanych tabel jednocześnie. LAST_INSERT_ID() zwraca ID ostatnio wstawionego rekordu — niezbędne przy powiązaniach.

Konfiguracja XAMPP + phpMyAdmin

środowisko pracy
01

Uruchom XAMPP

Otwórz XAMPP Control Panel → kliknij Start przy Apache i MySQL. Oba moduły muszą świecić zielono.

02

Otwórz phpMyAdmin

W przeglądarce: localhost/phpmyadmin
Login: root, hasło: puste.

03

Utwórz bazę

Zakładka SQL → wklej skrypt CREATE → kliknij Wykonaj. Następnie wklej INSERT z danymi.

04

Wykonaj zapytanie

Wybierz bazę sklep_online → SQL → wklej → Ctrl+Enter. Ctrl+Space = autouzupełnianie.

05

Eksport / Import

Eksport: Zaznacz bazę → Eksport → Format SQL → Wykonaj.
Import: Nowa baza → Import → wybierz .sql.

Przydatne polecenia MySQL CLI:
mysql -u root -p — logowanie  ·  SHOW DATABASES; — lista baz  ·  USE sklep_online; — wybór bazy
SHOW TABLES; — lista tabel  ·  DESCRIBE klienci; — struktura tabeli  ·  EXIT; — wyjście