Najpopularniejsze pytania rekrutacyjne z baz danych i SQL (z odpowiedziami)

Mateusz Bogolubow

04-11-2026

(aktualizacja: 04-11-2026)

JOIN, GROUP BY i HAVING, NULL, indeksy, transakcje i klasyczne zadania z SQL. Sprawdź, jakie pytania o bazy danych padają na rozmowach na juniora.

Najpopularniejsze pytania rekrutacyjne z baz danych i SQL (z odpowiedziami)
Na rozmowach z baz danych i SQL najczęściej pada kilkanaście pytań: czym różni się WHERE od HAVING, INNER JOIN od LEFT JOIN, jak działa NULL, do czego służą indeksy i czym są transakcje (ACID). Rekruter sprawdza nie składnię z pamięci, tylko czy rozumiesz, jak baza przetwarza zapytanie i co robi z brakującymi danymi. Dobra odpowiedź to jedno zdanie definicji i krótkie zapytanie, które to pokazuje.

Rozmowa o bazach danych rzadko jest egzaminem z całej dokumentacji. Najczęściej to te same kilkanaście tematów, przy których rekruter sprawdza, czy rozumiesz, jak baza liczy wynik, a nie tylko czy potrafisz skopiować zapytanie z internetu. Poniżej znajdziesz pytania, które wracają na rozmowach na stanowisko juniora, razem z tym, jak na nie odpowiadać. Jeśli dopiero uczysz się łączenia tabel, najpierw przeczytaj artykuł o tym, jak łączyć tabele w SQL i czym są relacje, a ten tekst potraktuj jako listę kontrolną. Pytania o język, w którym piszesz aplikację, są osobno: o Pythona i JavaScript, bo na rozmowie z backendu pytania o SQL padają obok nich.

Wszystkie zapytania uruchomiliśmy w SQLite 3.51.0, dlatego wyniki podane w tekście to faktyczne wyniki, a nie zgadywanie. Składnia w MySQL czy PostgreSQL jest w podstawach taka sama, ale niektóre szczegóły różnią się między silnikami, więc tam, gdzie to ważne, o tym piszemy.

Jak odpowiadać na pytania rekrutacyjne z SQL?

Najlepiej w trzech krokach: jedno zdanie definicji, krótkie zapytanie lub przykład z życia (np. „klienci bez zamówień”) i informacja, kiedy to ma znaczenie w praktyce. Taka odpowiedź zajmuje 30–60 sekund i pokazuje, że rozumiesz mechanizm, a nie wkułeś formułkę. Przy zadaniach z kodem mów na głos, co chcesz policzyć, zanim napiszesz pierwszą linijkę: rekruterzy oceniają sposób myślenia, a nie tylko wynik.

W przykładach używamy dwóch tabel: departments (działy: IT, HR, Finanse) i employees:

idnamesalarydepartment_idmanager_id
1Ala90001 (IT)NULL
2Bartek70001 (IT)1
3Celina70002 (HR)1
4Darek50002 (HR)3
5EwaNULLNULL1
6Filip80001 (IT)1

Pytania są ułożone od podstaw do tematów, które częściej pojawiają się przy ofertach na mid-poziom.

Podstawy baz danych

Czym jest relacyjna baza danych i czym różni się od NoSQL?

Relacyjna baza danych przechowuje dane w tabelach (wiersze i kolumny), a tabele łączy ze sobą relacjami przez klucze. Dostęp do danych odbywa się w języku SQL, a schemat (kolumny i ich typy) jest ustalony z góry. Bazy NoSQL (np. MongoDB, Redis) to szersza grupa: dokumenty, pary klucz-wartość, grafy, często z elastycznym schematem. Dobra odpowiedź nie rozstrzyga „co lepsze”, tylko mówi, kiedy co wybrać: relacyjna sprawdza się, gdy dane są powiązane i liczy się spójność (zamówienia, płatności, użytkownicy), a NoSQL tam, gdzie liczy się elastyczność kształtu danych albo skala odczytu.

Jakie są rodzaje poleceń SQL?

Zwykle dzieli się je na grupy:

GrupaDo czego służyPrzykłady
DDL (definicja struktury)tworzenie i zmiana tabelCREATE, ALTER, DROP
DML (zmiana danych)dodawanie, zmiana i usuwanie wierszyINSERT, UPDATE, DELETE
DQL (zapytania)odczyt danychSELECT
DCL (uprawnienia)kto co możeGRANT, REVOKE
TCL (transakcje)kontrola transakcjiBEGIN, COMMIT, ROLLBACK

Na rozmowie rzadko trzeba wymieniać wszystko, ale podział na DDL i DML pada często, zwłaszcza w pytaniu o różnicę między DELETE i DROP (niżej).

Czym różni się klucz główny od obcego i unikalnego?

Klucz główny (primary key) jednoznacznie identyfikuje wiersz w tabeli: jest unikalny i nie może być pusty. Klucz obcy (foreign key) to kolumna, która wskazuje na klucz główny innej tabeli i pilnuje, żeby nie powstał wiersz „osierocony”. Ograniczenie UNIQUE pilnuje unikalności wartości, ale tabela może mieć takich kolumn kilka, a wartość może być pusta. W naszym teście próba dodania pracownika z nieistniejącym działem skończyła się błędem FOREIGN KEY constraint failed, a drugi dział o nazwie IT błędem UNIQUE constraint failed: departments.name. Usunięcie działu, do którego należą pracownicy, też zostało odrzucone.

Uwaga praktyczna: w SQLite klucze obce trzeba włączyć poleceniem PRAGMA foreign_keys = ON, w MySQL czy PostgreSQL działają od razu. Szersze omówienie relacji (jeden do wielu, wiele do wielu, tabela pośrednia) znajdziesz w artykule o relacjach i JOIN w SQL.

Zapytania i łączenie tabel

Czym różni się INNER JOIN od LEFT JOIN?

INNER JOIN zwraca tylko wiersze, które mają dopasowanie po obu stronach. LEFT JOIN zwraca wszystkie wiersze z lewej tabeli, a tam, gdzie dopasowania nie ma, wstawia NULL. W naszych danych INNER JOIN pominął Ewę, bo nie ma przypisanego działu, a LEFT JOIN od strony działów pokazał też dział Finanse z zerem pracowników:

SELECT d.name, COUNT(e.id) AS cnt
FROM departments d
LEFT JOIN employees e ON e.department_id = d.id
GROUP BY d.id;
name     cnt
-------  ---
IT       3
HR       2
Finanse  0

Zwróć uwagę na COUNT(e.id) zamiast COUNT(*): pierwsze nie liczy wierszy z NULL, dlatego dział bez pracowników ma 0, a nie 1. Typowe pytanie uzupełniające: „czy istnieje RIGHT JOIN?”. Tak, to lustrzane odbicie LEFT JOIN, ale w praktyce zamienia się kolejność tabel i używa LEFT.

Jak znaleźć rekordy, które nie mają dopasowania w drugiej tabeli?

Klasyczny wzorzec: LEFT JOIN i filtr IS NULL po kolumnie z prawej tabeli. To pytanie typu „znajdź klientów bez zamówień”.

SELECT e.name
FROM employees e
LEFT JOIN departments d ON d.id = e.department_id
WHERE d.id IS NULL;

W naszym teście wynik to jeden wiersz: Ewa. Alternatywą jest NOT EXISTS (o nim niżej, przy NOT IN).

Co to jest self join i do czego go używać?

To złączenie tabeli z samą sobą, stosowane przy danych hierarchicznych, np. pracownik i jego przełożony zapisany w manager_id. Tabelę trzeba wtedy opatrzyć dwoma różnymi aliasami:

SELECT e.name, m.name AS manager
FROM employees e
LEFT JOIN employees m ON m.id = e.manager_id;

Wynik: Ala nie ma przełożonego (NULL), Bartek, Celina, Ewa i Filip mają Alę, a Darek ma Celinę. LEFT JOIN jest tu ważny, bo INNER JOIN pominąłby Alę, czyli szefa wszystkich.

Czym różni się UNION od UNION ALL?

UNION łączy wyniki dwóch zapytań i usuwa duplikaty, a UNION ALL zostawia wszystko. W teście to samo zapytanie połączone samo ze sobą dało przez UNION jeden wiersz, a przez UNION ALL sześć (3 + 3). UNION ALL jest szybszy, bo nie musi porównywać wierszy, więc używa się go, gdy duplikaty nie są możliwe albo są pożądane.

Filtrowanie, grupowanie i NULL

Czym różni się WHERE od HAVING?

WHERE filtruje wiersze przed grupowaniem, a HAVING filtruje grupy po agregacji. W WHERE nie użyjesz funkcji agregujących, np. COUNT(*), bo wtedy grupy jeszcze nie istnieją.

SELECT department_id, COUNT(*) AS c, AVG(salary) AS a
FROM employees
WHERE salary > 5000
GROUP BY department_id
HAVING COUNT(*) >= 2;

Wynik w naszym teście: jedna grupa, dział 1 (IT), c = 3, a = 8000.0. WHERE najpierw odrzucił Darka (5000) i Ewę (NULL), potem dane pogrupowano, a HAVING odrzucił dział HR, który miał już tylko jedną osobę (Celinę).

W jakiej kolejności baza wykonuje zapytanie?

Logicznie (nie zawsze fizycznie, bo optymalizator może zmieniać plan) kolejność jest inna niż w zapisie:

KrokKlauzulaCo się dzieje
1FROM / JOINwybór i złączenie tabel
2WHEREfiltrowanie wierszy
3GROUP BYgrupowanie
4HAVINGfiltrowanie grup
5SELECTwyliczenie kolumn i aliasów
6ORDER BYsortowanie
7LIMITucięcie wyniku

Stąd wynika, dlaczego w standardowym SQL nie użyjesz aliasu z SELECT w WHERE (SELECT wykonuje się później), a w ORDER BY możesz. Uwaga: SQLite akurat na to zezwala i w naszym teście WHERE yearly > 80000 zadziałało. W innych silnikach to samo zapytanie zwykle zakończy się błędem, więc nie opieraj na tym kodu.

Co robi NULL i dlaczego = NULL nie działa?

NULL oznacza „brak wartości, nie wiem” i nie jest równy niczemu, nawet drugiemu NULL. Każde porównanie z NULL daje wynik nieznany, a WHERE przepuszcza tylko wiersze, dla których warunek jest prawdziwy. Dlatego sprawdzamy IS NULL lub IS NOT NULL:

SELECT name FROM employees WHERE salary = NULL;     -- zero wierszy
SELECT name FROM employees WHERE salary IS NULL;    -- Ewa
SELECT name FROM employees WHERE salary <> 7000;    -- Ala, Darek, Filip

Trzeci przykład to częste zaskoczenie: Ewa (salary równe NULL) nie trafia do wyniku <> 7000, mimo że „nie zarabia 7000”. Porównanie z NULL nie jest ani prawdziwe, ani fałszywe, więc wiersz odpada. Jeśli chcesz ją uwzględnić, napisz salary <> 7000 OR salary IS NULL.

Czym różni się COUNT(*) od COUNT(kolumna)?

COUNT(*) liczy wiersze, a COUNT(kolumna) tylko te wiersze, w których kolumna nie jest NULL. Funkcje SUM i AVG też pomijają NULL.

SELECT COUNT(*) AS all_rows, COUNT(salary) AS with_salary,
       SUM(salary) AS total, AVG(salary) AS average
FROM employees;
all_rows  with_salary  total  average
--------  -----------  -----  -------
6         5            36000  7200.0

Średnia to 7200, bo 36000 / 5, a nie 36000 / 6. To dobra odpowiedź na pytanie „czy wiesz, co AVG robi z pustymi wartościami”.

Dlaczego NOT IN z NULL zwraca pusty wynik?

Bo x NOT IN (1, 2, NULL) jest rozwijane do x <> 1 AND x <> 2 AND x <> NULL, a ostatni warunek nigdy nie jest prawdziwy. Jeśli podzapytanie zwróci choć jedno NULL, całość nie zwróci żadnego wiersza. W naszym teście department_id ma NULL (Ewa), więc zapytanie „działy bez pracowników” przez NOT IN zwróciło zero wierszy, mimo że dział Finanse jest pusty. Dwa poprawne sposoby dały Finanse:

-- wersja 1: odfiltruj NULL
SELECT name FROM departments
WHERE id NOT IN (SELECT department_id FROM employees WHERE department_id IS NOT NULL);

-- wersja 2: NOT EXISTS (bezpieczniejsza)
SELECT d.name FROM departments d
WHERE NOT EXISTS (SELECT 1 FROM employees e WHERE e.department_id = d.id);

Tej pułapce poświęciliśmy też fragment artykułu o relacjach i JOIN w SQL. Rekruterzy lubią ją, bo kod „wygląda dobrze” i działa na danych testowych bez NULL.

Klasyczne zadania z kodem

Jak znaleźć duplikaty w tabeli?

Pogrupuj po kolumnie i zostaw grupy, które mają więcej niż jeden wiersz:

SELECT salary, COUNT(*) AS c
FROM employees
GROUP BY salary
HAVING COUNT(*) > 1;

W naszych danych wynik to 7000 z licznikiem 2 (Bartek i Celina). Pytanie uzupełniające: „jak usunąć duplikaty, zostawiając jeden wiersz?”. Odpowiedź zależy od silnika, ale schemat jest podobny: wybierasz do zostawienia wiersz z najmniejszym id w grupie i usuwasz resztę, najpierw sprawdzając wynik SELECTem.

Jak znaleźć drugie największe wynagrodzenie?

Najczęstsze zadanie z kodem na rozmowach. Dwa popularne rozwiązania:

-- podzapytanie: największe wśród mniejszych niż maksimum
SELECT MAX(salary) FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);

-- sortowanie: pomiń pierwszą wartość
SELECT DISTINCT salary FROM employees
WHERE salary IS NOT NULL
ORDER BY salary DESC
LIMIT 1 OFFSET 1;

Oba zwróciły 8000. Znaczenie ma DISTINCT: bez niego przy dwóch osobach z najwyższą pensją „drugie” w drugim zapytaniu dałoby tę samą kwotę. Dopowiedz też, co zrobisz z NULL i z remisami; rekruter często dopytuje właśnie o to. LIMIT/OFFSET nie działa w każdej bazie (SQL Server używa TOP lub OFFSET … FETCH), więc dobrze znać dwa sposoby.

Jak wybrać najlepiej zarabiającą osobę w każdym dziale?

Tu przydają się funkcje okna (window functions), np. ROW_NUMBER() z PARTITION BY, które numerują wiersze osobno w każdej grupie bez zwijania ich w jeden wiersz jak GROUP BY:

SELECT name, department_id, salary
FROM (
  SELECT *,
         ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn
  FROM employees
  WHERE department_id IS NOT NULL
)
WHERE rn = 1;

Wynik w teście: Ala (IT, 9000) i Celina (HR, 7000). Funkcje okna działają w MySQL od wersji 8 i w PostgreSQL, ale w starszych instalacjach ich nie ma, więc wiedza o GROUP BY z podzapytaniem nadal się przydaje.

Czym różnią się ROW_NUMBER, RANK i DENSE_RANK?

Różnica ujawnia się przy remisach. Dwie osoby z pensją 7000 (Bartek i Celina) dały w teście:

namesalaryROW_NUMBERRANKDENSE_RANK
Ala9000111
Filip8000222
Bartek7000333
Celina7000433
Darek5000554

ROW_NUMBER numeruje zawsze unikalnie (remis rozstrzyga kolejność przypadkowa), RANK daje remisującym ten sam numer i zostawia „dziurę” (po dwóch trójkach jest piątka), a DENSE_RANK nie robi dziur (po trójkach czwórka). „Trzecie największe wynagrodzenie” policzysz więc najlepiej przez DENSE_RANK.

Projektowanie i wydajność

Co to jest normalizacja i kiedy ją łamać?

Normalizacja (normalization) to porządkowanie schematu tak, żeby ta sama informacja była zapisana w jednym miejscu. W skrócie: pierwsza postać normalna (1NF) wymaga jednej wartości w komórce (bez listy po przecinku), druga (2NF) i trzecia (3NF) usuwają zależności kolumn od części klucza i od innych kolumn niebędących kluczem. Korzyść: zmiana nazwy działu to jedna aktualizacja, a nie tysiąc. Denormalizację (celowe powtarzanie danych) stosuje się, gdy odczyt jest ważniejszy niż prostota zapisu, np. w raportach, i wtedy trzeba pilnować spójności. Częsty błąd z życia to lista identyfikatorów rozdzielona przecinkami w jednej kolumnie: nie da się jej sensownie filtrować ani złączyć, dlatego robi się tabelę pośrednią.

Co to jest indeks i jak działa?

Indeks (index) to dodatkowa struktura (zwykle drzewo B), która pozwala znaleźć wiersze bez przeglądania całej tabeli, podobnie jak skorowidz w książce. Przyspiesza odczyt po kolumnie, ale spowalnia zapis, bo przy każdym INSERT, UPDATE i DELETE indeks trzeba zaktualizować, i zajmuje miejsce. Efekt widać w planie zapytania. Na tabeli z 50 000 wierszy zapytanie po adresie e-mail najpierw wykonało pełny przegląd tabeli (SCAN), a po założeniu indeksu wyszukiwanie po indeksie (SEARCH ... USING INDEX):

EXPLAIN QUERY PLAN SELECT * FROM big WHERE email = 'u777@x.pl';
-- przed: SCAN big
CREATE INDEX idx_big_email ON big(email);
-- po:    SEARCH big USING INDEX idx_big_email (email=?)

W innych silnikach polecenie nazywa się EXPLAIN lub EXPLAIN ANALYZE. Warto je znać, bo odpowiedź „sprawdzę plan zapytania” jest dużo lepsza niż „dodam indeks”.

Kiedy indeks nie zadziała?

Gdy zapytanie nie może z niego skorzystać. Nasz indeks na email nie pomógł w dwóch przypadkach: przy wzorcu zaczynającym się od znaku wieloznacznego (LIKE '%777@x.pl') oraz gdy kolumnę opakowano w funkcję (WHERE upper(email) = 'U777@X.PL'). W obu planach wróciło SCAN big. Zasada: indeks działa, gdy porównujesz samą kolumnę, a wzorzec LIKE ma stały początek ('u777%'). Pytanie uzupełniające dotyczy kolejności kolumn w indeksie złożonym: najpierw te, po których filtrujesz równością.

Czym różni się indeks klastrowy od nieklastrowego?

W indeksie klastrowym (clustered) sam układ wierszy w tabeli jest uporządkowany według klucza, więc może być tylko jeden na tabelę (zwykle klucz główny). Indeks nieklastrowy to osobna struktura ze wskaźnikami do wierszy i może ich być wiele. Szczegóły zależą od silnika (w SQL Server i MySQL InnoDB klastrowy jest domyślnie na kluczu głównym, w PostgreSQL tabela nie jest utrzymywana w kolejności), więc na rozmowie wystarczy zasada ogólna i uczciwe „zależy od silnika”. Tego tematu nie sprawdzaliśmy testem.

Co to jest problem N+1?

To sytuacja, gdy aplikacja wykonuje jedno zapytanie o listę (np. 100 zamówień), a potem po jednym zapytaniu dla każdego elementu (100 zapytań o klientów), zamiast jednego z JOIN. Powstaje najczęściej przy używaniu ORM, bo kod wygląda niewinnie (order.customer). Rozwiązanie to jedno zapytanie z JOIN lub wczytanie powiązanych danych hurtem. Przykład z ORM w Pythonie i błąd, który z tego wynika, opisujemy w artykule o modelach, relacjach i sesji SQLAlchemy 2 w FastAPI.

Transakcje

Czym są transakcje i co oznacza ACID?

Transakcja to grupa operacji wykonanych jako całość: albo wszystkie się udają, albo żadna. ACID to cztery właściwości, które baza ma gwarantować:

LiteraZnaczeniePo ludzku
A (atomicity)atomowośćwszystko albo nic
C (consistency)spójnośćpo transakcji dane spełniają ograniczenia (klucze, CHECK)
I (isolation)izolacjarównoległe transakcje nie psują sobie nawzajem wyniku
D (durability)trwałośćzatwierdzone zmiany przetrwają awarię

Klasyczny przykład to przelew. Zdejmujemy 80 zł z konta A i dodajemy 80 zł na konto B. Nasze konto A miało 100 zł i ograniczenie CHECK (bal >= 0), więc próba zdjęcia 80 zł dwa razy w jednej transakcji skończyła się błędem CHECK constraint failed: bal>=0, a po ROLLBACK saldo wróciło do 100. Poprawna transakcja (BEGIN, dwie zmiany, COMMIT) dała 20 na koncie A i 130 na koncie B. Bez transakcji awaria między dwoma UPDATE zostawiłaby pieniądze zdjęte, a niedodane.

Po co są poziomy izolacji transakcji?

Pełna izolacja kosztuje wydajność, więc bazy pozwalają wybrać kompromis. Poziomy (od najsłabszego): READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE. Im wyższy, tym mniej anomalii (brudny odczyt, odczyt niepowtarzalny, odczyt fantomowy), ale tym więcej blokad i konfliktów. Domyślny poziom różni się między silnikami (zwykle READ COMMITTED w PostgreSQL, REPEATABLE READ w MySQL InnoDB). Na poziomie juniora wystarczy znać nazwy, wiedzieć, że istnieje kompromis między spójnością a wydajnością, i potrafić podać jeden przykład anomalii. Tego tematu nie sprawdzaliśmy testem, opisujemy go na podstawie dokumentacji silników.

Czym różni się DELETE od TRUNCATE i DROP?

DELETE usuwa wybrane wiersze (z WHERE) albo wszystkie, ale struktura tabeli zostaje, a operację można objąć transakcją. TRUNCATE (nie ma go w SQLite, więc nie testowaliśmy) usuwa wszystkie wiersze naraz, nie przyjmuje WHERE i zwykle działa szybciej, a w części silników nie da się go cofnąć tak jak DELETE. DROP TABLE usuwa całą tabelę razem ze strukturą. W teście DELETE FROM acc zostawiło pustą tabelę (COUNT(*) równe 0), do której dało się dodać nowy wiersz, a po DROP TABLE każde zapytanie kończyło się błędem no such table. Rekruter oczekuje też ostrzeżenia: DELETE i UPDATE bez WHERE zmieniają całą tabelę, więc najpierw sprawdza się warunek zapytaniem SELECT.

Bezpieczeństwo i praktyka

Czym jest SQL injection i jak się przed nim bronić?

To atak, w którym dane od użytkownika trafiają do zapytania jako kod SQL, bo zapytanie zbudowano przez sklejanie tekstu. Obrona to zapytania parametryzowane (prepared statements), w których baza dostaje osobno kod i osobno wartości, a w ORM używanie jego mechanizmów zamiast ręcznego sklejania. Walidacja danych jest dodatkiem, a nie zamiennikiem. Atak, przykłady w Pythonie, PHP i Next.js oraz pułapki (ORDER BY, LIKE, IN) opisujemy w artykule o tym, czym jest SQL injection i jak się przed nim bronić.

Czym jest ORM i czy trzeba znać SQL, skoro jest ORM?

ORM (Object-Relational Mapping, np. SQLAlchemy, Eloquent, Prisma) pozwala operować na obiektach zamiast na ręcznie pisanych zapytaniach i zwykle zabezpiecza przed podstawowymi wersjami SQL injection. Znajomość SQL jest jednak potrzebna: żeby zrozumieć, jakie zapytanie ORM wygeneruje, wykryć problem N+1, napisać raport, którego ORM nie obsługuje, i zrozumieć migracje. O zmianach schematu w praktyce piszemy w artykule o migracjach z Alembic i autogenerate. Dobra odpowiedź nie zestawia ich jako „albo-albo”, tylko pokazuje, że ORM to nakładka na SQL.

Jakie zadania z bazy danych pojawiają się na rozmowach?

Najczęściej: napisanie zapytania na kilku tabelach (JOIN z agregacją), znalezienie duplikatów, drugie lub n-te największe wynagrodzenie, „klienci bez zamówień” (anty-join), top N w grupie, a przy zadaniach projektowych narysowanie schematu prostej aplikacji (sklep, biblioteka, rezerwacje) z kluczami i relacjami. Rekruter patrzy, czy czytasz dane, zanim zaczniesz pisać, czy pytasz o NULL i duplikaty i czy sprawdzasz wynik na małym zestawie danych.

Co zwróci zapytanie? Mini-test na rozgrzewkę

Rekruterzy lubią pytanie „ile wierszy zwróci to zapytanie?”. Spróbuj odpowiedzieć na trzy poniższe na danych z tabeli employees z początku artykułu, zanim zajrzysz do odpowiedzi. Wszystkie uruchomiliśmy w SQLite.

Zadanie 1. Co zwróci to zapytanie?

SELECT COUNT(*), COUNT(salary), AVG(salary) FROM employees;

Odpowiedź: 6, 5 i 7200.0. COUNT(salary) i AVG pomijają NULL (Ewa), więc średnia to 36000 / 5.

Zadanie 2. Kogo zwróci to zapytanie?

SELECT name FROM employees WHERE salary <> 7000;

Odpowiedź: Alę, Darka i Filipa (trzy wiersze). Ewa z NULL odpada, bo porównanie z NULL nie jest prawdziwe. Bartek i Celina odpadają, bo zarabiają dokładnie 7000.

Zadanie 3. Które działy nie mają pracowników?

SELECT name FROM departments
WHERE id NOT IN (SELECT department_id FROM employees);

Odpowiedź: żaden. Zapytanie zwróci zero wierszy, bo department_id ma NULL (Ewa), a NOT IN z NULL nigdy nie jest prawdziwe. Poprawny wynik (Finanse) da NOT EXISTS albo NOT IN z filtrem WHERE department_id IS NOT NULL.

Jak przygotować się do rozmowy z SQL?

Samo czytanie odpowiedzi nie wystarczy, bo pytania uzupełniające szybko pokazują, czy rozumiesz temat. Skuteczniej działa założenie własnej małej bazy (SQLite wystarczy), wpisanie wszystkich przykładów z tego artykułu i przepisanie ich na swoje dane. Dla porządku:

ObszarCo umieć wyjaśnić własnymi słowami
Podstawytabela, klucz główny i obcy, UNIQUE, relacje, SQL a NoSQL
ZapytaniaINNER vs LEFT JOIN, anty-join, self join, UNION vs UNION ALL
AgregacjaGROUP BY, WHERE vs HAVING, COUNT(*) vs COUNT(kolumna), kolejność wykonania
NULLIS NULL, <> i NULL, NOT IN a NOT EXISTS, AVG i puste wartości
Zadaniaduplikaty, drugie największe, top N w grupie, ROW_NUMBER/RANK/DENSE_RANK
Wydajnośćindeks, plan zapytania, kiedy indeks nie działa, N+1
TransakcjeACID, COMMIT/ROLLBACK, poziomy izolacji, DELETE vs TRUNCATE vs DROP

Na czym juniorzy najczęściej tracą punkty?

Nie na trudnych tematach, tylko na trzech drobiazgach, które wychodzą dopiero przy pytaniach uzupełniających:

  1. NULL traktowany jak zwykła wartość. = NULL, NOT IN z kolumną, która ma puste wartości, i AVG bez myślenia o brakach dają wyniki, które wyglądają dobrze. Nie wystarczy napisać „użyję IS NULL”. Wyjaśnij, że porównanie z NULL nie jest ani prawdziwe, ani fałszywe, więc wiersz odpada.
  2. Mylenie WHERE z HAVING i zapominanie o kolejności wykonania. Kto rozumie, że WHERE działa przed grupowaniem, a HAVING po, nie pomyli ich nigdy, a to dobry dowód, że rozumiesz, jak baza liczy wynik.
  3. Indeks jako „magiczna poprawka”. „Dodam indeks” bez sprawdzenia planu zapytania i bez wiedzy, kiedy indeks nie zadziała (funkcja na kolumnie, LIKE '%…'), brzmi jak wyuczona formułka.

Na co zwraca uwagę mentor, gdy sprawdza zapytania SQL?

Przy code review zapytań mentor zwykle zaczyna od trzech rzeczy, które pasują też do rozmowy rekrutacyjnej.

Pierwsze: „Co się stanie, gdy w tej kolumnie będzie NULL?” To pytanie pada przy cudzym zapytaniu niemal za każdym razem, bo większość błędów w SQL wynika z pustych wartości: NOT IN, <>, AVG, COUNT(kolumna). Zapytanie, które działa na danych testowych bez NULL, często psuje się dopiero na prawdziwych danych. Kto zadaje to pytanie sam sobie przed oddaniem zapytania, oszczędza mentorowi poprawek.

Drugie: „Pokaż mi plan zapytania, zanim dodasz indeks.” Odruchowe „dodam indeks” brzmi jak wyuczona formułka. Lepszy porządek to sprawdzić plan (EXPLAIN), zobaczyć, czy zapytanie przegląda całą tabelę, i dopiero wtedy zdecydować o indeksie, pamiętając, że funkcja na kolumnie lub LIKE '%…' i tak go nie wykorzysta.

Trzecie: ćwicz na własnej, małej bazie z celowo „brudnymi” danymi. Zadania z SQL najlepiej robić na kilkunastu wierszach, w których sam umieścisz NULL, duplikaty i pracownika bez działu. Wtedy widzisz wynik każdego zapytania na oko i od razu wyłapujesz, że INNER JOIN zgubił wiersz albo że HAVING odrzucił grupę. Na gotowej, czystej bazie z kursu te pułapki się nie ujawniają.

Jak brzmi dobra odpowiedź, a jak słaba?

Weźmy pytanie o WHERE i HAVING:

OdpowiedźDlaczego
Słaba„HAVING to taki WHERE do GROUP BY.”To skrót, który niczego nie wyjaśnia. Pytanie „dlaczego nie użyję COUNT(*) w WHERE?” od razu to pokaże.
Dobra„WHERE filtruje wiersze przed grupowaniem, więc nie ma w nim funkcji agregujących. HAVING filtruje już gotowe grupy, np. działy, w których jest co najmniej dwóch pracowników z pensją powyżej 5000.”Jest różnica w momencie działania, konsekwencja i przykład.

Ten sam schemat działa przy każdym pytaniu z tego artykułu: definicja, mechanizm, przykład użycia.

Co zrobić, gdy nie znasz odpowiedzi?

Nie zgaduj i nie milcz. Powiedz, co wiesz na pewno, a potem rozumuj na głos, np.: „Nie pamiętam dokładnej nazwy poziomu izolacji, ale wiem, że chodzi o kompromis między spójnością a wydajnością, więc sprawdziłbym to w dokumentacji bazy, której używamy.” Taka odpowiedź pokazuje, że potrafisz sobie poradzić w pracy, a umiejętność znalezienia odpowiedzi jest w codziennej pracy ważniejsza niż pamięć do szczegółów. Przy zadaniu z kodem możesz też poprosić o kilka wierszy przykładowych danych i na nich sprawdzić swoje zapytanie.

Dobrze też wiedzieć, jak wygląda sama rozmowa. W podcaście Maciek Stęga opowiada o tym, jak przejść rekrutację w IT.

Znajomość odpowiedzi to połowa sukcesu, drugą jest znalezienie rozmów, na które warto się umówić. Jeśli szukasz ofert, sięgnij po darmowy poradnik o tym, jak szukać pracy w 2026 roku, w którym zebraliśmy 3 sposoby poszukiwania pracy, które u naszych podopiecznych dają najlepsze efekty. Całość procesu, od CV po rozmowę, opisuje artykuł o tym, jak przejść cały proces rekrutacyjny na juniora, a o zadaniach i portfolio piszemy w poradniku jak przygotować się do rekrutacji na juniora developera.

Jeśli braki wyjdą przy JOIN-ach albo transakcjach, to sygnał, że warto uporządkować wiedzę z mentorem. W kursie PHP jest moduł o projektowaniu bazy MySQL i bezpiecznych zapytaniach przez PDO, a w kursie Python są osobne moduły o bazach danych: podstawy baz danych w Pythonie i zaawansowana praca z bazą w Pythonie. Zadania i projekty z tej tematyki sprawdza mentor w code review.

Konsultacja i warsztaty devmentor.pl

Masz pytania?

Partnerzy

Firmy, z którymi współpracujemy

To partnerzy odpowiedzialni za staże po naszych kursach. Organizują je dla najlepszych absolwentów.

Zobacz wszystkich partnerów →
  • VR Medical
  • Sophona
  • Robot Force