Zapytania SQL w kwalifikacji INF.03: najważniejsze zasady, przykłady i 227 powiązanych pytań w banku.
Zapytanie SQL opisuje, jaki wynik ma powstać, a nie sposób przeglądania tabel. Serwer sam ustala kolejność odczytu, dlatego cała precyzja tkwi w warunku, grupowaniu i sortowaniu. Punkty na egzaminie 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 niżej dotyczą MySQL i opierają się na dwóch tabelach: klient(id, nazwisko, miasto) oraz zamowienie(id, klient_id, kwota, data).
WHERE z LIKE, IN i BETWEEN oraz sortowanie wyniku
WHERE odsiewa pojedyncze wiersze, zanim powstanie wynik. We wzorcu LIKE znak procent zastępuje dowolny ciąg znaków, także pusty, a podkreślenie dokładnie jeden znak. Warunek nazwisko LIKE 'Kow%' dopasuje Kowalski i Kowalczyk, ale nie Nowak, natomiast wzorzec '_owak' pasuje do Nowak i nie pasuje do Kowalczyk. W MySQL domyślne porównywanie tekstu pomija wielkość liter, więc wzorzec 'kow%' da ten sam wynik co 'Kow%'.
IN skraca listę porównań: miasto IN ('Gdańsk', 'Sopot') znaczy dokładnie tyle, co miasto = 'Gdańsk' OR miasto = 'Sopot'. BETWEEN 100 AND 500 obejmuje obie granice, więc kwota równa 100 oraz kwota równa 500 wchodzą do wyniku. Bez ORDER BY kolejność wierszy nie jest gwarantowana, a ORDER BY kwota DESC sortuje malejąco; zapis ORDER BY miasto, kwota DESC porządkuje najpierw według miasta, a wewnątrz miasta od najwyższej kwoty.
NULL nie jest zerem ani pustym tekstem
NULL oznacza brak wartości, a nie zero i nie pusty tekst. Porównanie kwota = NULL nie zwróci żadnego wiersza, bo jego wynik jest nieznany, a nie prawdziwy; działają wyłącznie zapisy kwota IS NULL oraz kwota IS NOT NULL. Warunek kwota <> 100 również pomija wiersze z pustą kwotą, więc pytanie o wszystkie zamówienia inne niż 100 trzeba rozszerzyć do postaci kwota <> 100 OR kwota IS NULL.
NULL zmienia też wynik funkcji agregujących. Dla dziesięciu zamówień, z których dwa mają pustą kwotę, COUNT(*) zwróci 10, a COUNT(kwota) zwróci 8. SUM i AVG pomijają NULL, więc średnia z wartości 200, NULL i 400 wynosi 300, ponieważ serwer dzieli 600 przez 2, a nie przez 3. Gdyby brak miał być liczony jak zero, trzeba napisać AVG(COALESCE(kwota, 0)), co da 200.
GROUP BY i różnica między WHERE a HAVING
GROUP BY zwija wiersze w grupy i wylicza jedną wartość dla każdej z nich. Zapytanie SELECT klientid, COUNT(*) AS ile, SUM(kwota) AS suma FROM zamowienie GROUP BY klientid daje jeden wiersz dla każdego klienta, który ma co najmniej jedno zamówienie, a klient bez zamówień nie pojawi się wcale. W liście SELECT mogą stać tylko kolumny wymienione w GROUP BY oraz funkcje agregujące, bo dla pozostałych kolumn serwer nie wie, którą z wielu wartości grupy pokazać.
WHERE działa przed grupowaniem, HAVING po nim. Warunek WHERE data >= '2026-01-01' odrzuci starsze zamówienia, zanim powstaną grupy, więc sumy obejmą tylko bieżący rok. HAVING SUM(kwota) > 1000 zostawi wyłącznie tych klientów, u których policzona suma przekracza 1000, i tego warunku nie da się przenieść do WHERE, ponieważ w chwili filtrowania pojedynczych wierszy suma jeszcze nie istnieje.
INNER JOIN i LEFT JOIN na tych samych danych
INNER JOIN zwraca tylko pary spełniające warunek złączenia. Gdy w tabeli klient jest 50 wierszy, 8 osób nie ma żadnego zamówienia, a pozostałe 42 osoby złożyły łącznie 100 zamówień, zapytanie klient INNER JOIN zamowienie ON zamowienie.klient_id = klient.id zwróci 100 wierszy dotyczących 42 klientów. Liczbę wierszy wyznacza tabela zamówień, więc klient z trzema zamówieniami wystąpi trzy razy, a listę nazwisk bez powtórzeń daje dopiero SELECT DISTINCT.
LEFT JOIN zachowuje każdy wiersz z lewej tabeli, a kolumny z prawej wypełnia wartością NULL, gdy pary brakuje. Przy tych samych danych wynik liczy 108 wierszy: 100 dopasowanych par oraz 8 klientów z pustym zamowienie.id, a warunek WHERE zamowienie.id IS NULL wypisze wyłącznie tych ośmiu. Przeniesienie tego samego warunku do ON zmienia sens zapytania, bo ON ogranicza tylko prawą tabelę i w wyniku zostanie wszystkich 50 klientów z pustymi kolumnami zamówienia.
Podzapytania i pułapka NOT IN
Podzapytanie to zapytanie osadzone w innym. Zapis SELECT nazwisko FROM klient WHERE id IN (SELECT klient_id FROM zamowienie WHERE kwota > 1000) wypisze każdego klienta, który ma co najmniej jedno zamówienie powyżej 1000, i pokaże go raz, nawet gdy takich zamówień ma kilka. Gdy podzapytanie zwraca jedną liczbę, porównuje się je wprost, na przykład WHERE kwota > (SELECT AVG(kwota) FROM zamowienie), a operator > z podzapytaniem zwracającym wiele wierszy kończy się błędem.
Jeśli podzapytanie po NOT IN zwróci choć jeden NULL, całe zapytanie nie odda żadnego wiersza, bo porównanie z nieznaną wartością nigdy nie daje wyniku prawda. Zdarza się to zawsze wtedy, gdy kolumna klient_id dopuszcza puste wartości i taki wiersz faktycznie istnieje. Bezpieczniejsze są NOT EXISTS albo LEFT JOIN z warunkiem IS NULL po prawej stronie, ponieważ oba działają poprawnie mimo pustych wartości.
INSERT, UPDATE, DELETE i szkielet CREATE TABLE
Polecenie INSERT INTO klient (nazwisko, miasto) VALUES ('Nowak', 'Sopot') dodaje jeden wiersz, a kolumny pominięte dostają wartość domyślną lub NULL; jeżeli pominięta kolumna ma NOT NULL i nie ma wartości domyślnej, serwer zgłosi błąd i nic nie zapisze. UPDATE zamowienie SET kwota = 0 bez WHERE zmieni wszystkie wiersze tabeli, a DELETE FROM zamowienie bez WHERE usunie je co do jednego. Przed taką zmianą trzeba uruchomić SELECT z identycznym warunkiem i sprawdzić, ile wierszy on obejmuje.
Szkielet tabel w MySQL wygląda następująco: CREATE TABLE klient (id INT AUTOINCREMENT PRIMARY KEY, nazwisko VARCHAR(50) NOT NULL, miasto VARCHAR(50)) oraz CREATE TABLE zamowienie (id INT AUTOINCREMENT PRIMARY KEY, klientid INT NOT NULL, kwota DECIMAL(10,2), data DATE, FOREIGN KEY (klientid) REFERENCES klient(id)). Typ DECIMAL(10,2) mieści wartości do 99999999.99 i liczy pieniądze dokładnie, czego typ FLOAT nie gwarantuje. Klucz obcy blokuje wstawienie zamówienia dla nieistniejącego klienta oraz usunięcie klienta, który ma zamówienia, i wymaga silnika InnoDB.
Z tym działem powiązano 227 pytań w banku INF.03. Na ekranie Wszystkie pytania wybierz jeden z tagów: sql. Możesz wtedy przejrzeć zadania tylko z interesującego Cię obszaru.