🎯 Cele lekcji

📋

1. Rodzaje zapytań SQL

TypOpisPrzykład
ProsteStałe warunki — zwraca zawsze te same wierszeSELECT * FROM produkty WHERE aktywny = 1
ParametryczneParametry wypełniane dynamicznie przez użytkownikaWHERE cena < ? AND kategoria = ?
KrzyżoweDane pogrupowane wierszami i kolumnami (pivot)Sprzedaż per miesiąc i per kategoria
PodsumowująceAgregacje — COUNT, SUM, AVG per grupyRaport: suma sprzedaży per region
🔒

2. Zapytania parametryczne — Prepared Statements

Prepared Statement to zapytanie SQL z parametrami (? lub :nazwa). Chroni przed SQL Injection — wartości parametrów są przekazywane oddzielnie od struktury zapytania.
-- PHP PDO — zapytanie parametryczne
$stmt = $pdo->prepare(
    'SELECT * FROM produkty WHERE cena < ? AND id_kategorii = ?'
);
$stmt->execute([$maxCena, $idKat]);
$wyniki = $stmt->fetchAll();

-- Z nazwanymi parametrami
$stmt = $pdo->prepare(
    'SELECT * FROM klienci WHERE email = :email AND aktywny = :aktywny'
);
$stmt->execute([':email' => $email, ':aktywny' => 1]);

-- MySQL: PREPARE/EXECUTE (po stronie serwera)
PREPARE stmt FROM 'SELECT * FROM produkty WHERE cena < ? AND kategoria = ?';
SET @max = 500, @kat = 'Elektronika';
EXECUTE stmt USING @max, @kat;
DEALLOCATE PREPARE stmt;
Nigdy nie buduj zapytań przez konkatenację zmiennych!
ZLE: $sql = "SELECT * FROM klienci WHERE email = '$email'";
Podatne na SQL Injection: $email = "' OR 1=1; DROP TABLE klienci; --"
📊

3. Raporty SQL

-- Raport: sprzedaż per kategoria i miesiąc
SELECT
    k.nazwa AS kategoria,
    DATE_FORMAT(z.data, '%Y-%m') AS miesiac,
    COUNT(*) AS liczba_transakcji,
    SUM(pz.ilosc * pz.cena_jedn) AS przychod
FROM kategorie k
JOIN produkty p   ON k.id_kategorii = p.id_kategorii
JOIN pozycje_zamowien pz ON p.id_produktu = pz.id_produktu
JOIN zamowienia z   ON pz.id_zamowienia = z.id_zamowienia
GROUP BY k.nazwa, DATE_FORMAT(z.data, '%Y-%m')
ORDER BY miesiac, przychod DESC;

-- Top 10 klientów po wartości zamówień
SELECT k.imie, k.nazwisko, k.email,
       COUNT(z.id_zamowienia) AS liczba_zam,
       SUM(z.kwota) AS laczna_wartosc
FROM klienci k
INNER JOIN zamowienia z ON k.id_klienta = z.id_klienta
GROUP BY k.id_klienta, k.imie, k.nazwisko, k.email
ORDER BY laczna_wartosc DESC
LIMIT 10;

4. Indeksy i EXPLAIN

-- Tworzenie indeksu na często filtrowanej kolumnie
CREATE INDEX idx_klienci_email ON klienci(email);
CREATE INDEX idx_zamowienia_data ON zamowienia(data);
CREATE INDEX idx_produkty_kat_cena ON produkty(kategoria, cena);

-- EXPLAIN — analiza planu wykonania zapytania
EXPLAIN SELECT * FROM klienci WHERE email = 'jan@mail.com';

-- Kluczowe kolumny w EXPLAIN:
-- type: ALL=pełny skan (wolny), ref/eq_ref=indeks (szybki)
-- key: użyty indeks (NULL = brak indeksu!)
-- rows: szacowana liczba przeglądanych wierszy
Wartość typeOpisWydajność
ALLPełny skan tabeliNajgorsza
indexSkan całego indeksuSłaba
rangeZakres indeksu (BETWEEN, >, <)Dobra
refIndeks nieunikalnyDobra
eq_refIndeks unikalny (JOIN)Bardzo dobra
constKlucz główny lub UNIQUE z wartościąNajlepsza
✏️

Zadania interaktywne

Zadanie 1Quiz: po co używa się EXPLAIN?

Do czego służy polecenie EXPLAIN SELECT ...?

  • Wyświetla definicję tabeli (jak DESCRIBE)
  • Pokazuje plan wykonania zapytania — czy używa indeksów, ile wierszy przegląda
  • Tłumaczy zapytanie SQL na język naturalny
  • Wykonuje zapytanie i wyświetla czas jego trwania
Zadanie 2Napisz zapytanie raportowe: top 10 klientów

Napisz zapytanie zwracające top 10 klientów według łącznej wartości ich zamówień (imię, nazwisko, suma zamówień):

Zadanie 3Zapytanie proste vs parametryczne

Które stwierdzenie o zapytaniach parametrycznych (prepared statements) jest PRAWDZIWE?

  • Zapytania parametryczne są wolniejsze, bo MySQL musi je za każdym razem parsować od nowa
  • Chronią przed SQL Injection, bo wartości parametrów są przekazywane oddzielnie od struktury zapytania
  • Można ich używać tylko z operatorem WHERE = (równość)
  • Prepared statements nie są obsługiwane przez MySQL

📌 Zapamiętaj