🎯 Cele lekcji

🔍

1. Czym jest podzapytanie?

Podzapytanie (subquery) to zapytanie SELECT zagnieżdżone wewnątrz innego zapytania SQL. Wynik podzapytania jest używany przez zapytanie zewnętrzne.
TypZwracaGdzie używane
SkalarneJedną wartość (1 wiersz, 1 kolumna)WHERE, SELECT, HAVING
WierszoweJeden wiersz, wiele kolumnWHERE z operatorem wiersza
TabelaryczneWiele wierszy i kolumnIN, NOT IN, FROM (derived table)
KorelowaneDla każdego wiersza zewnętrznego osobnoWHERE EXISTS, WHERE col = (subq)
-- Skalarne: produkty droższe od średniej ceny
SELECT nazwa, cena
FROM produkty
WHERE cena > (SELECT AVG(cena) FROM produkty);

-- Tabelaryczne z IN: klienci, którzy złożyli zamówienie
SELECT imie, nazwisko
FROM klienci
WHERE id_klienta IN (
    SELECT DISTINCT id_klienta FROM zamowienia
);
🔗

2. IN, NOT IN, EXISTS, NOT EXISTS

-- NOT IN: klienci BEZ żadnego zamówienia
SELECT imie, nazwisko
FROM klienci
WHERE id_klienta NOT IN (
    SELECT DISTINCT id_klienta FROM zamowienia
    WHERE id_klienta IS NOT NULL  -- UWAGA: NOT IN z NULL daje zawsze FALSE!
);

-- EXISTS: czy klient złożył co najmniej 1 zamówienie?
SELECT k.imie, k.nazwisko
FROM klienci k
WHERE EXISTS (
    SELECT 1 FROM zamowienia z
    WHERE z.id_klienta = k.id_klienta  -- korelacja z zewnętrznym!
);

-- NOT EXISTS: klienci bez zamówień (bezpieczniejsze niż NOT IN)
SELECT k.imie, k.nazwisko
FROM klienci k
WHERE NOT EXISTS (
    SELECT 1 FROM zamowienia z
    WHERE z.id_klienta = k.id_klienta
);
NOT IN z NULL — pułapka! Jeśli podzapytanie zwraca choć jedną wartość NULL, NOT IN zawsze zwróci pusty wynik. Używaj NOT EXISTS lub filtruj NULL: WHERE id_klienta IS NOT NULL.
📋

3. Podzapytanie w FROM i w SELECT

-- Podzapytanie w FROM (derived table / tabela pochodna)
SELECT kategoria, srednia_cena
FROM (
    SELECT kategoria, AVG(cena) AS srednia_cena
    FROM produkty
    GROUP BY kategoria
) AS statystyki   -- alias jest wymagany!
WHERE srednia_cena > 300;

-- Podzapytanie w SELECT (skalarne)
SELECT
    k.imie, k.nazwisko,
    (SELECT COUNT(*) FROM zamowienia z
     WHERE z.id_klienta = k.id_klienta) AS ile_zamowien
FROM klienci k;
JOIN vs Subquery: JOIN jest zazwyczaj wydajniejszy niż podzapytanie korelowane (wykonywane raz per wiersz). Podzapytanie w FROM (derived table) jest często równie wydajne co JOIN. Używaj tego, co jest bardziej czytelne, chyba że masz problem z wydajnością.
✏️

Zadania interaktywne

Zadanie 1Identyfikuj typ podzapytania

Które z poniższych zapytań zawiera skalarne podzapytanie?

  • WHERE id IN (SELECT id FROM produkty WHERE cena > 100)
  • WHERE cena > (SELECT AVG(cena) FROM produkty)
  • WHERE EXISTS (SELECT 1 FROM zamowienia WHERE id_klienta = k.id)
  • FROM (SELECT * FROM produkty GROUP BY kategoria) AS t
Zadanie 2Quiz: EXISTS vs IN

Które stwierdzenie o wydajności EXISTS vs IN jest prawdziwe?

  • IN jest zawsze szybszy niż EXISTS
  • EXISTS zatrzymuje się po znalezieniu pierwszego dopasowania (short-circuit), co może być szybsze przy dużych podzbiorach
  • EXISTS i IN są zawsze identyczne wydajnościowo
  • NOT IN jest bezpieczniejsze niż NOT EXISTS w obecności wartości NULL
Zadanie 3Napisz zapytanie z NOT IN

Tabele: klienci(id_klienta, imie, nazwisko), zamowienia(id_zamowienia, id_klienta, data). Napisz zapytanie zwracające klientów, którzy NIGDY nie złożyli zamówienia:

📌 Zapamiętaj