SQL testy rekrutacyjne – jak się przygotować i co warto wiedzieć?
- Jak odpowiadać na pytania techniczne?
- Fundamenty, które najczęściej sprawdzają SQL testy rekrutacyjne
- NULL i logika trójwartościowa
- WHERE, GROUP BY i HAVING
- Logiczna kolejność wykonywania zapytania
- JOIN i kontrola liczby wierszy
- Jak rozwiązywać zadanie SQL na żywo?
- Na jakie błędy zwrócić uwagę?
- Jak pokazać świadomość wydajności?
- SARGability i funkcje w WHERE
- EXPLAIN jako narzędzie diagnostyczne
- Plan przygotowania na ostatnie dni
W testach rekrutacyjnych z SQL rekruter zwykle nie ocenia pamięciowej znajomości składni. Sprawdza, czy rozumiesz relacje między danymi, potrafisz przewidzieć wynik zapytania i umiesz zauważyć pułapki związane z NULL, JOIN oraz wydajnością.
Jak odpowiadać na pytania techniczne?
SQL jest językiem deklaratywnym. Określasz, jaki wynik chcesz uzyskać, a silnik bazy dobiera sposób wykonania zapytania. Dlatego podczas rozmowy liczy się nie tylko gotowy kod, lecz także tok rozumowania.
Dobrą odpowiedź możesz zbudować według prostego schematu:
- Definicja – wyjaśnij pojęcie jednym lub dwoma zdaniami.
- Przykład – pokaż krótkie zapytanie albo sytuację, na przykład znalezienie klientów bez zamówień.
- Kontekst – powiedz, kiedy dane rozwiązanie ma znaczenie dla poprawności, wydajności lub modelu danych.
Przy zadaniu praktycznym mów na głos. Zamiast od razu pisać kod, powiedz: „Najpierw sprawdzę relację między tabelami, potem odfiltruję wiersze, następnie wykonam agregację”. Rekruter widzi wtedy, czy potrafisz kontrolować proces, a nie tylko odtworzyć zapamiętany wzorzec.
Fundamenty, które najczęściej sprawdzają SQL testy rekrutacyjne
NULL i logika trójwartościowa
NULL nie oznacza pustego tekstu, zera ani konkretnej wartości. Oznacza brak informacji lub wartość nieznaną. W SQL porównanie z NULL nie zwraca TRUE ani FALSE, lecz UNKNOWN. Klauzula WHERE przepuszcza wyłącznie wiersze, dla których warunek jest prawdziwy.
SELECT name
FROM employees
WHERE salary = NULL;
To zapytanie nie znajdzie pracowników bez wynagrodzenia. Do sprawdzania braku wartości służą specjalne operatory:
SELECT name
FROM employees
WHERE salary IS NULL;
SELECT name
FROM employees
WHERE salary IS NOT NULL;
Ta sama zasada wyjaśnia, dlaczego poniższe zapytanie pomija osoby z NULL:
SELECT name
FROM employees
WHERE salary <> 7000;
Jeżeli chcesz uwzględnić także brak wynagrodzenia, musisz zapisać warunek jawnie:
SELECT name
FROM employees
WHERE salary <> 7000
OR salary IS NULL;
Na rozmowie zwróć uwagę także na funkcje agregujące. COUNT(*) liczy wiersze, natomiast COUNT(salary) pomija rekordy, w których salary ma wartość NULL. AVG i SUM również nie uwzględniają NULL w obliczeniach.
WHERE, GROUP BY i HAVING
Te trzy elementy odpowiadają za różne etapy pracy z danymi. WHERE filtruje pojedyncze wiersze przed grupowaniem, GROUP BY tworzy grupy, a HAVING filtruje gotowe grupy, często na podstawie funkcji agregującej.
| Operacja | Działa na | Kiedy stosować |
|---|---|---|
| WHERE | Pojedyncze wiersze | Gdy chcesz odrzucić dane przed agregacją |
| GROUP BY | Wartości grupujące | Gdy chcesz policzyć lub podsumować dane osobno dla każdej grupy |
| HAVING | Gotowe grupy | Gdy warunek zależy od COUNT, SUM, AVG, MIN lub MAX |
Przykład łączy filtrowanie pracowników z agregacją i filtrowaniem grup:
SELECT department_id,
COUNT(*) AS employee_count,
AVG(salary) AS average_salary
FROM employees
WHERE salary > 5000
GROUP BY department_id
HAVING COUNT(*) >= 2;
Najpierw odrzucane są osoby zarabiające 5000 lub mniej. Następnie pozostali pracownicy są grupowani według działu, a na końcu zostają tylko działy z co najmniej dwiema osobami.
Logiczna kolejność wykonywania zapytania
Kolejność klauzul w kodzie nie odpowiada kolejności logicznego przetwarzania. Rekruter może poprosić o jej wyjaśnienie, ponieważ pomaga ona zrozumieć, dlaczego alias z SELECT zwykle nie jest dostępny w WHERE.
- FROM i JOIN – wybór tabel oraz ich połączenie.
- WHERE – filtrowanie pojedynczych wierszy.
- GROUP BY – utworzenie grup.
- HAVING – filtrowanie grup.
- SELECT – wyliczenie zwracanych kolumn.
- ORDER BY – sortowanie wyniku.
- LIMIT lub odpowiednik – ograniczenie liczby zwróconych wierszy.
To kolejność logiczna. Optymalizator może wykonać poszczególne operacje fizycznie w inny sposób, jeżeli zachowa ten sam wynik.
JOIN i kontrola liczby wierszy
INNER JOIN zwraca wyłącznie dopasowane rekordy. LEFT JOIN zachowuje wszystkie wiersze z lewej tabeli, nawet gdy po prawej stronie nie ma dopasowania. Wtedy kolumny prawej tabeli przyjmują wartość NULL.
| Rodzaj JOIN | Co zwraca? | Kiedy stosować? |
|---|---|---|
| INNER JOIN | Tylko rekordy dopasowane w obu tabelach | Gdy interesuje Cię część wspólna danych |
| LEFT JOIN | Wszystkie rekordy z lewej tabeli oraz dopasowania z prawej | Gdy chcesz zachować także rekordy bez relacji |
| FULL JOIN | Rekordy dopasowane i niedopasowane z obu tabel | Gdy potrzebujesz pełnego zestawienia obu zbiorów |
Jeżeli chcesz znaleźć klientów bez zamówień, możesz użyć wzorca LEFT JOIN i IS NULL:
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id
WHERE o.id IS NULL;
Ważne jest rozumienie kardynalności. Połączenie tabeli klientów z tabelą pozycji zamówienia może zwielokrotnić wiersze. Zanim użyjesz DISTINCT, sprawdź, czy problemem nie jest błędne połączenie albo brak wcześniejszej agregacji.
Jak rozwiązywać zadanie SQL na żywo?
Największy błąd podczas zadania praktycznego polega na pisaniu zapytania bez sprawdzenia, co właściwie znajduje się w tabelach. Zacznij od schematu, kluczy i oczekiwanej liczby rekordów.
- Przeanalizuj schemat – sprawdź klucze główne, obce, relacje jeden do wielu oraz kolumny, które mogą zawierać NULL.
- Opisz rozwiązanie na głos – powiedz, które wiersze filtrujesz, jakie tabele łączysz i czy agregacja ma nastąpić przed czy po JOIN.
- Zbuduj prosty SELECT – wybierz kilka kolumn i sprawdź, czy podstawowy wynik jest poprawny.
- Dodawaj kolejne elementy – najpierw JOIN, potem WHERE, GROUP BY, HAVING, funkcje okienkowe i sortowanie.
- Sprawdź przypadki brzegowe – pomyśl o braku dopasowania, NULL, remisach i pustej tabeli.
Przykładowe zadanie może brzmieć: „Zwróć trzech najlepiej zarabiających klientów w każdym regionie”. Rozwiązanie wymaga najpierw policzenia wydatków klienta, a dopiero potem nadania rankingu osobno w każdym regionie.
WITH customer_totals AS (
SELECT customer_id,
SUM(amount) AS total_amount
FROM orders
GROUP BY customer_id
),
ranked_customers AS (
SELECT c.region,
c.id,
c.name,
ct.total_amount,
ROW_NUMBER() OVER (
PARTITION BY c.region
ORDER BY ct.total_amount DESC
) AS position
FROM customer_totals ct
JOIN customers c
ON c.id = ct.customer_id
)
SELECT region, id, name, total_amount
FROM ranked_customers
WHERE position <= 3
ORDER BY region, total_amount DESC;
CTE porządkuje obliczenia, a funkcja okienkowa pozwala utworzyć ranking bez redukowania danych do jednego wiersza na region. W PostgreSQL, MySQL i SQL Server podstawowa składnia jest podobna, lecz szczegóły, takie jak ograniczanie wyniku, mogą się różnić. PostgreSQL i MySQL używają zwykle LIMIT, natomiast SQL Server oferuje między innymi TOP oraz OFFSET ... FETCH.
Na jakie błędy zwrócić uwagę?
W testach SQL często nie przegrywa się przez brak znajomości trudnej funkcji, lecz przez nieuwzględnienie jednego szczegółu w danych.
| Błąd | Dlaczego jest problemem? | Jak go naprawić? |
|---|---|---|
| Użycie DISTINCT po JOIN | Może ukrywać zwielokrotnienie wierszy i utratę informacji | Sprawdź warunek JOIN, kardynalność i rozważ wcześniejszą agregację |
| NOT IN z podzapytaniem zawierającym NULL | Logika trójwartościowa może sprawić, że zapytanie nie zwróci żadnego wiersza | Użyj NOT EXISTS albo odfiltruj NULL w podzapytaniu |
| COUNT(*) po LEFT JOIN | Pusta relacja może zostać policzona jako jeden wiersz wynikający z JOIN | Użyj COUNT(id) po stronie dołączanej tabeli |
| UPDATE lub DELETE bez WHERE | Operacja zmieni lub usunie wszystkie rekordy | Najpierw uruchom SELECT z tym samym warunkiem |
Pułapkę NOT IN dobrze ilustruje przykład:
SELECT d.name
FROM departments d
WHERE d.id NOT IN (
SELECT e.department_id
FROM employees e
);
Jeżeli choć jeden pracownik ma department_id = NULL, podzapytanie zawiera NULL. Wtedy porównanie staje się nieznane i wynik może być pusty. Bezpieczniejszą wersją jest:
SELECT d.name
FROM departments d
WHERE NOT EXISTS (
SELECT 1
FROM employees e
WHERE e.department_id = d.id
);
Jak pokazać świadomość wydajności?
Indeks jest dodatkową strukturą danych, często opartą na B-tree, która może przyspieszyć wyszukiwanie, sortowanie lub łączenie. Nie jest jednak darmowy. Zajmuje miejsce i musi być aktualizowany podczas INSERT, UPDATE oraz DELETE.
Na rozmowie nie mów automatycznie „dodam indeks”. Lepsza odpowiedź brzmi: „Najpierw sprawdzę plan wykonania, selektywność warunku i częstotliwość zapisu, a dopiero potem zdecyduję o indeksie”.
- Indeks może pomóc przy warunkach w
WHERE, kolumnach używanych wJOINoraz sortowaniu. - Kolejność kolumn w indeksie złożonym ma znaczenie, szczególnie przy filtrach po kilku polach.
- Indeks może nie pomóc, gdy filtr obejmuje dużą część tabeli.
- Indeks zwiększa koszt operacji zapisu, więc nie powinien być tworzony bez analizy użycia.
SARGability i funkcje w WHERE
SARGability oznacza zapis warunku w formie, która pozwala optymalizatorowi wykorzystać indeks. Problemem jest między innymi opakowanie indeksowanej kolumny w funkcję:
SELECT *
FROM orders
WHERE YEAR(created_at) = 2025;
Lepszy zapis wykorzystuje zakres wartości:
SELECT *
FROM orders
WHERE created_at >= '2025-01-01'
AND created_at < '2026-01-01';
Podobna uwaga dotyczy LIKE. Wzorzec zaczynający się od znaku wieloznacznego, taki jak LIKE '%abc', zwykle nie pozwala na efektywne użycie zwykłego indeksu B-tree. Ostateczną decyzję zawsze warto potwierdzić planem wykonania.
EXPLAIN jako narzędzie diagnostyczne
EXPLAIN pokazuje plan, według którego silnik zamierza wykonać zapytanie. W PostgreSQL możesz użyć także EXPLAIN ANALYZE, które uruchamia zapytanie i pokazuje rzeczywiste czasy oraz liczbę przetworzonych wierszy.
EXPLAIN ANALYZE
SELECT e.name, d.name
FROM employees e
JOIN departments d
ON d.id = e.department_id
WHERE e.salary > 7000;
W planie możesz zobaczyć między innymi pełny skan tabeli, skan indeksu oraz strategie takie jak Nested Loop, Hash Join lub Merge Join. Seq Scan nie zawsze oznacza błąd. Przy małej tabeli pełne odczytanie danych może być tańsze niż użycie indeksu. Liczy się kontekst i rzeczywisty koszt planu.
Plan przygotowania na ostatnie dni
Najlepiej ćwiczyć na małej bazie, w której samodzielnie umieścisz dane problematyczne. Kilka tabel wystarczy, aby przećwiczyć większość schematów pojawiających się w testach.
- Utwórz tabele customers, orders, order_items i products z kluczami oraz relacjami.
- Dodaj rekordy bez dopasowania, duplikaty i wartości NULL.
- Przetestuj INNER JOIN, LEFT JOIN, NOT EXISTS, GROUP BY oraz HAVING.
- Sprawdź różnicę między COUNT(*) i COUNT(kolumna).
- Uruchom EXPLAIN przed dodaniem indeksu i po jego utworzeniu.
Ćwicząc, nie ograniczaj się do uzyskania poprawnego wyniku. Wyjaśniaj sobie, dlaczego wynik wygląda właśnie tak. Sprawdź, co stanie się po dodaniu NULL, usunięciu dopasowania albo zmianie relacji jeden do wielu. Taki sposób pracy przygotowuje nie tylko do kodowania, lecz także do pytań uzupełniających.


