Zapytania SQL w kwalifikacji INF.03: najważniejsze zasady, przykłady i powiązane pytania w banku. Skrót działu, który otwiera osiem lekcji o SQL od SELECT po import bazy.
Zapytanie SQL opisuje, jaki wynik ma powstać, a nie sposób przeglądania tabel. Cała precyzja tkwi w warunku, grupowaniu i sortowaniu. Punkty przepadają najczęściej na drobiazgach: pominięty WHERE, porównanie z NULL, HAVING użyty zamiast WHERE, klucz obcy pominięty w definicji tabeli.
Wszystkie przykłady dotyczą MySQL i dwóch tabel:
klient(id, nazwisko, miasto)
zamowienie(id, klient_id, kwota, data)
WHERE z LIKE, IN i BETWEEN oraz sortowanie wyniku
We wzorcu LIKE procent zastępuje dowolny ciąg znaków (także pusty), a podkreślenie dokładnie jeden znak:
'Kow%'
Kowalski, Kowalczyk
Nowak
W MySQL domyślne porównywanie tekstu pomija wielkość liter, więc 'kow%' da ten sam wynik co 'Kow%'.
IN skraca listę porównań, a BETWEEN obejmuje obie granice:
WHERE miasto IN ('Gdańsk', 'Sopot')
-- dokladnie to samo co:
WHERE miasto = 'Gdańsk' OR miasto = 'Sopot'
WHERE kwota BETWEEN 100 AND 500
-- 100 i 500 WCHODZA do wyniku
Bez ORDER BY kolejność wierszy nie jest gwarantowana. Sortowanie po dwóch kolumnach czyta się od lewej:
ORDER BY miasto, kwota DESC
-- najpierw alfabetycznie po miescie,
-- a wewnatrz jednego miasta od najwyzszej kwoty
NULL nie jest zerem ani pustym tekstem
NULL znaczy „wartość nieznana", więc każde zwykłe porównanie z nim daje wynik nieznany, a nie prawdę:
WHERE kwota = NULL -- ZERO wierszy, nawet gdy puste kwoty istnieja
WHERE kwota IS NULL -- tak sie to robi
WHERE kwota IS NOT NULL
WHERE kwota <> 100 -- pomija wiersze z pusta kwota
WHERE kwota <> 100 OR kwota IS NULL -- dopiero to znaczy "wszystkie inne niz 100"
NULL zmienia też wynik agregacji. Dla dziesięciu zamówień, z których dwa mają pustą kwotę:
AVG z wartości 200, NULL, 400
300 (600 przez 2, nie przez 3)
AVG(COALESCE(kwota, 0)) z tych samych
200
GROUP BY i różnica między WHERE a HAVING
SELECT klient_id, COUNT(*) AS ile, SUM(kwota) AS suma
FROM zamowienie
WHERE data >= '2026-01-01'
GROUP BY klient_id
HAVING SUM(kwota) > 1000;
GROUP BY zwija wiersze w grupy i wylicza jedną wartość dla każdej. Klient bez zamówień nie pojawi się wcale. W liście SELECT mogą stać tylko kolumny z GROUP BY i funkcje agregujące. WHERE działa przed grupowaniem (odrzuca starsze zamówienia, zanim powstaną sumy), HAVING po nim: warunku na sumę nie da się przenieść do WHERE, bo przy filtrowaniu pojedynczych wierszy suma jeszcze nie istnieje.
INNER JOIN i LEFT JOIN na tych samych danych
Dane: 50 klientów, 8 osób bez zamówień, pozostałe 42 osoby złożyły łącznie 100 zamówień.
Zapytanie
Wierszy w wyniku
klient INNER JOIN zamowienie ON zamowienie.klient_id = klient.id
100 (42 klientów)
klient LEFT JOIN zamowienie ...
108 (100 par + 8 klientów z NULL)
jak wyżej plus WHERE zamowienie.id IS NULL
8
Ostatni wiersz tabeli to gotowy wzorzec na pytanie „kto nic nie zamówił":
SELECT klient.nazwisko
FROM klient
LEFT JOIN zamowienie ON zamowienie.klient_id = klient.id
WHERE zamowienie.id IS NULL;
Liczbę wierszy INNER JOIN wyznacza tabela zamówień: klient z trzema zamówieniami wystąpi trzy razy, a listę nazwisk bez powtórzeń daje SELECT DISTINCT. Uwaga na miejsce warunku: przeniesiony z WHERE do ON ogranicza tylko prawą tabelę i w wyniku zostanie wszystkich 50 klientów.
Podzapytania i pułapka NOT IN
SELECT nazwisko FROM klient
WHERE id IN (SELECT klient_id FROM zamowienie WHERE kwota > 1000);
Wypisze każdego klienta z co najmniej jednym zamówieniem powyżej 1000, raz, nawet gdy ma ich kilka.
Gdy podzapytanie zwraca JEDNĄ liczbę, porównuje się je wprost:
SELECT * FROM zamowienie
WHERE kwota > (SELECT AVG(kwota) FROM zamowienie);
Operator > z podzapytaniem wielowierszowym kończy się błędem. Osobna pułapka dotyczy NOT IN: jeśli podzapytanie zwróci choć jeden NULL, całe zapytanie nie odda żadnego wiersza, bo porównanie z nieznaną wartością nigdy nie daje prawdy. Bezpieczniejsze są NOT EXISTS albo pokazany wyżej LEFT JOIN z IS NULL, bo działają poprawnie mimo pustych wartości.
INSERT, UPDATE, DELETE
INSERT INTO klient (nazwisko, miasto) VALUES ('Nowak', 'Sopot');
UPDATE zamowienie SET kwota = 0 WHERE id = 7;
DELETE FROM zamowienie WHERE id = 7;
Przy INSERT kolumny pominięte dostają wartość domyślną lub NULL, a pominięta kolumna NOT NULL bez domyślnej to błąd.
Przy UPDATE i DELETE warunek WHERE jest jedyną rzeczą, która ratuje tabelę:
UPDATE zamowienie SET kwota = 0; -- zmieni WSZYSTKIE wiersze
DELETE FROM zamowienie; -- usunie tabele co do wiersza
Przed taką zmianą uruchom SELECT z identycznym warunkiem i zobacz, ile wierszy obejmie.
Szkielet CREATE TABLE z kluczem obcym
CREATE TABLE klient (
id INT AUTO_INCREMENT PRIMARY KEY,
nazwisko VARCHAR(50) NOT NULL,
miasto VARCHAR(50)
);
CREATE TABLE zamowienie (
id INT AUTO_INCREMENT PRIMARY KEY,
klient_id INT NOT NULL,
kwota DECIMAL(10,2),
data DATE,
FOREIGN KEY (klient_id) REFERENCES klient(id)
);
DECIMAL(10,2) mieści wartości do 99999999.99 i liczy pieniądze dokładnie, czego FLOAT nie gwarantuje. Klucz obcy blokuje wstawienie zamówienia dla nieistniejącego klienta i usunięcie klienta z zamówieniami; wymaga silnika InnoDB.
Projektowanie relacji i normalizację opisuje artykuł o bazach danych, wywołania z PHP artykuł o PHP, a pełne zadania znajdziesz w arkuszach INF.03. Lekcje działu
Każde zapytanie z tych lekcji wykonasz w przeglądarce, na wspólnej bazie w placu zabaw SQL.