Блог Stepik про учебу и карьеру

SQL-собеседование без паники: что реально спрашивают, где валятся даже сильные, и как подготовиться так, чтобы продать себя

В этой статье вместе с Дмитрием Кузьминым, автором курсов по SQL на Stepik и участником множества технических интервью, мы разберём, какие типичные ловушки встречаются кандидатам. Представим вашему вниманию два практических мини-кейса и короткие «шпоры». Ну а в конце вас ждут рекомендации по подготовке и способы тренироваться на задачах, максимально близких к реальным.

Как это обычно проходит

SQL-собеседования в большинстве компаний устроены похоже: обычно это онлайн-созвон, где вы вместе с интервьюером смотрите на один экран и разбираете задачи в реальном времени. Формат может отличаться — кто-то сразу даёт практику, кто-то начинает с разговор, но в целом логика у всех примерно одна: сначала проверяют базовые знания, потом переходят к реальным задачам.
Чаще всего всё начинается довольно спокойно: интервьюер уточняет ваш опыт, пару слов о проектах, чтобы понимать, на каком уровне держать планку вопросов. И уже после этого предлагает пройти короткий «разогрев» на теории.
1) Скрининг (10–20 минут)
Первая короткая сессия чаще всего проходит с HR-специалистом или джун-интервьюером, который проверяет базовую теорию. Это 5–10 быстрых вопросов, на которые нужно ответить без долгих раздумий. Типовые формулировки могут быть такими:
  • «Левая таблица — 10 строк, правая — 5. Какое минимальное и максимальное число строк может получиться при разных JOIN
  • «Чем отличаются WHERE и HAVING
  • «Когда уместнее DISTINCT, а когда — GROUP BY
  • «В чём разница между JOIN и EXISTS
  • «Что произойдёт, если фильтр из ON перенести в WHERE, особенно в LEFT JOIN
На этом этапе важна не зубрёжка терминов, а то, насколько уверенно вы объясняете базовую логику. Если тут много пауз и сомнений, до задач иногда просто не доходят — так устроен этап отбора, не более.
2) Практическая часть (30–60 минут)
Если с теорией всё в порядке, переходят к задачам. Обычно дают 2–3 задачи: от простых к более объёмным.
Частая последовательность такая:
  • первая задача — на SELF JOIN или базовые агрегации;
  • следующие — на оконные функции (ROW_NUMBER, RANK, фреймы, работа с периодами и датами).
Формулировки чаще всего идут в духе: «Вот таблицы, вот вопрос аналитика — получите нужную выборку и объясните, что вы делаете и почему».
Здесь интервьюер смотрит не только на финальный код. Гораздо важнее, как вы рассуждаете: уточняете ли данные, комментируете ли шаги, видите ли крайние случаи. И да, нервничать абсолютно нормально. Никто не ждёт знания всего синтаксиса наизусть, но умение мыслить вслух и аккуратно проверять гипотезы ценится очень высоко.

Пять частых теоретических «пробоев»

Даже у опытных специалистов на собеседовании бывают короткие замирания на базе, не потому что они чего-то не знают, а потому что задачи формулируются быстро, а голова в этот момент занята стрессом. Вот пять мест, где чаще всего возникают те самые паузы.
1) Минимум/максимум строк при JOIN
Один из самых частых вопросов про JOIN — не о синтаксисе, а о кардинальности:
сколько строк мы вообще можем получить на выходе?
  • Для INNER JOIN минимум всегда 0.
  • Максимум — вплоть до L × R, если в ключах есть дубликаты.
  • Для LEFT JOIN минимум — всегда L (левая таблица сохраняет свои строки).
  • Максимум тоже может вырасти выше L, если в правой таблице дубликаты.
Главная мысль: кардинальность растёт не из-за JOIN, а из-за дубликатов. Это банально, но на стресс-интервью вспоминается далеко не сразу.
2) WHERE против HAVING
Эта пара вопросов всплывает почти всегда. И здесь важно помнить всего одно правило:
  • WHERE фильтрует строки до агрегации;
  • HAVING — после.
Ошибка, которая встречается снова и снова: попытка отфильтровать агрегат в WHERE.
На собеседовании интервьюеры обычно смотрят, как быстро человек замечает эту ловушку.
3) DISTINCT против GROUP BY — почему это не одно и то же
Обе конструкции связаны с уникальностью, но работают по-разному:
  • DISTINCT просто убирает дубликаты набора столбцов.
  • GROUP BY группирует строки и позволяет использовать агрегатные функции.
В задачах на вычисление метрик попытка решить всё через «DISTINCT и хватит» — одна из классических ошибок.
4) JOIN или EXISTS?
EXISTS — это не про объединение таблиц, а про проверку факта существования.
Он отлично работает:
  • в коррелированных подзапросах,
  • в анти-кейсах,
  • в ситуациях, когда не нужны поля второй таблицы, а важно только «есть запись или нет».
Интервьюеры часто спрашивают не «что такое EXISTS», а когда он уместнее JOIN, и почему.
5) Фильтр в ON и фильтр в WHERE (особенно в LEFT JOIN)
Это одна из самых частых ситуаций, где разваливается логика запроса:
  • Если фильтр поставить в WHERE, можно нечаянно «съесть» строки с NULL
  • и превратить LEFT JOIN в INNER JOIN.
  • Если фильтровать в ON, то «пустые» строки сохраняются, как и должно быть при левом соединении.
На собеседовании это проверяется почти всегда, и очень наглядно показывает, насколько кандидат понимает, как работает механизм соединения.

Мини-кейс A: фильтр в ON против фильтра в WHERE в LEFT JOIN

Это один из самых популярных примеров на собеседованиях, в нём быстро видно, понимает ли кандидат, как фильтрация влияет на тип JOIN. Формулировка обычно звучит довольно бытово:
Задача: вернуть всех клиентов и сумму их оплаченных транзакций. Если оплат не было — сумма должна быть 0.
Дальше интервьюеры смотрят, куда именно кандидат ставит фильтр по статусу транзакции.
Вот корректный подход — фильтровать оплаченные транзакции прямо в ON, чтобы не потерять клиентов без оплат:
SELECT c.client_id,
       COALESCE(SUM(t.amount), 0) AS total_paid
FROM clients c
LEFT JOIN transactions t
  ON t.client_id = c.client_id
 AND t.status = 'paid'
GROUP BY c.client_id;
Если же вынести фильтр в WHERE, логика ломается — LEFT JOIN по сути превращается в INNER JOIN, и строки клиентов без оплат исчезнут:
SELECT c.client_id,
       COALESCE(SUM(t.amount), 0) AS total_paid
FROM clients c
LEFT JOIN transactions t
  ON t.client_id = c.client_id
WHERE t.status = 'paid'
GROUP BY c.client_id;

Какие уточняющие вопросы часто задают после решения

Этот кейс почти всегда продолжают короткими доп-вопросами, чтобы понять, насколько глубоко вы понимаете механику.
1) «Как вывести только клиентов без оплат?»
Классическое решение:
SELECT c.client_id
FROM clients c
LEFT JOIN transactions t
  ON t.client_id = c.client_id
 AND t.status = 'paid'
WHERE t.client_id IS NULL;
2) «Почему здесь нужен COALESCE?»
Чтобы сумма для клиентов без транзакций не превращалась в NULL.
3) «Что с производительностью?»
Обычно обсуждают индексацию по ключам и статусам, раннюю фильтрацию (например, в CTE), а также аккуратность работы с дубликатами ключей при JOIN.

Мини-кейс B: окна — последняя транзакция и сумма по последним трём

Задачи на оконные функции — частые гости на собеседованиях. Обычно интервьюеры не просто хотят увидеть знание ROW_NUMBER или агрегатов по окну, а смотрят, понимает ли кандидат логику: что значит «последние», по какому признаку мы сортируем и как выбираем нужный диапазон.
Один из типичных примеров выглядит так:
Задача: для каждого клиента нужно определить дату последней транзакции и сумму трёх последних транзакций.
Решение чаще всего строят через ранжирование записей и накопление значений окна:
WITH ranked AS (
  SELECT
    t.client_id,
    t.trx_date,
    t.amount,
    ROW_NUMBER() OVER (PARTITION BY t.client_id ORDER BY t.trx_date DESC) AS rn,
    SUM(t.amount) OVER (
      PARTITION BY t.client_id
      ORDER BY t.trx_date DESC
      ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS sum_last3
  FROM transactions t
)
SELECT client_id,
       MAX(CASE WHEN rn = 1 THEN trx_date END)   AS last_trx_date,
       MAX(CASE WHEN rn = 1 THEN sum_last3 END)  AS sum_last3_trx
FROM ranked
GROUP BY client_id;
Здесь ROW_NUMBER помогает найти самую свежую запись на клиента,
а фрейм ROWS BETWEEN 2 PRECEDING AND CURRENT ROW аккуратно собирает сумму трёх последних значений.

Куда интервьюеры обычно «ныряют» глубже

После решения такие задачи редко заканчиваются одним только SQL. Интервьюеры часто проверяют, насколько кандидат чувствует механику окон — и меняют условия, чтобы посмотреть, как он перестроит решение.
1) «Сделай то же самое, но по месяцам — как витрину».
Тут уже появляются условные агрегаты, PIVOT или разворот через CASE WHEN.
Иногда дополнительно спрашивают, что делать с месяцами, где транзакций нет.
2) «А если сортировать не по дате, а по сумме?»
Тут проверяют понимание семантики: что считается «последним» и что происходит с окном, когда сортировка меняется.
Важно проговорить, какой критерий «последовательности» мы используем и почему.

Мини-кейс C: EXISTS против анти-джойна

Этот кейс проверяет понимание того, как искать отсутствие данных, а не их наличие. Интервьюеры любят такие задачи, потому что они показывают, насколько уверенно кандидат работает с подзапросами и понимает различия между EXISTS, NOT EXISTS и анти-джойном через LEFT JOIN.
Задача обычно звучит просто:
Найти клиентов, которые не совершали покупок за определённый период.
Самый прямой и часто самый чистый способ — использовать NOT EXISTS:
SELECT c.client_id
FROM clients c
WHERE NOT EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.client_id = c.client_id
    AND o.order_date BETWEEN DATE '2024-01-01' AND DATE '2024-01-31'
);
Эквивалентное решение можно построить и через LEFT JOIN с фильтром на NULL — классический анти-джойн:
SELECT c.client_id
FROM clients c
LEFT JOIN orders o
  ON o.client_id = c.client_id
 AND o.order_date BETWEEN DATE '2024-01-01' AND DATE '2024-01-31'
WHERE o.client_id IS NULL;
Обе конструкции работают одинаково с точки зрения результата, но на реальных данных между ними есть важные различия.
Чаще всего интервьюеры задают вопрос в сторону производительности. И здесь уместно отметить:
  • NOT EXISTS обычно ведёт себя предсказуемее на больших таблицах, особенно если индекс стоит на (client_id, order_date).
  • Для LEFT JOINIS NULL оптимизатор тоже может построить эффективный план, но иногда такой запрос тяжелее читается и сложнее масштабируется.
  • Если в проекте есть жёсткие требования к читаемости и поддержке запросов, NOT EXISTS часто становится предпочтительным вариантом именно из-за своей однозначной семантики.

Как готовиться и не сгореть

Хорошая подготовка к SQL-собеседованию — это не перечитывание статей, а регулярная практика. Теорию можно пролистать за вечер, но когда вы видите задачу «вживую», часто оказывается, что проверить гипотезу негде, данных мало, а времени — ещё меньше. Именно поэтому чтение полезно, но без практики легко впасть в ступор.
Один из рабочих подходов — условный режим «Бауманки», когда сначала даётся задача, и у вас есть время разобраться с ней любым удобным способом. Можно открыть документацию, поискать примеры, свериться с учебником — важно не угадать синтаксис, а понять сам приём и уметь потом объяснить, почему решение устроено именно так.
Для подготовки помогает небольшой, но стабильный темп: 30–60 минут в день.
Обычно хватает двух задач:
  • одна — на агрегации или JOIN,
  • вторая — на окна или EXISTS.
Такой режим хорошо тренирует мышление «как на интервью»: проговаривайте ход решения вслух, формулируйте допущения, проверяйте крайние случаи. Чем больше таких микро-репетиций, тем спокойнее чувствуешь себя на реальном собеседовании.
После решения полезно сделать короткий разбор, что можно упростить, где логика шла по вилке, каких анти-кейсов не хватило в первом варианте. Это формирует важную привычку — замечать собственные ошибки раньше, чем это сделает интервьюер.

Почему практические задачи в курсах выглядят «как в жизни», но не повторяют реальные собеседования

Практика, собранная в курсах Дмитрия Кузьмина, основана на реальных интервью: он фиксировал саму логику задач — какие навыки они проверяют, какие ловушки встречаются и в каком порядке обычно развивается рассуждение. При этом прямое копирование вопросов невозможно и неэтично, поэтому каждая задача пересобрана с нуля: другой контекст, другие данные, но та же сложность и тот же набор проверяемых навыков.
Задачи идут по уровням сложности — от junior до upper-middle, и внутри каждого уровня есть подсказки, разборы и дополнительные вопросы «от старшего аналитика». Всё это помогает не просто решить конкретный пример, а понять общий метод — тот самый, который потом пригождается на реальных собеседованиях.

Шпора перед собесом (распечатай на одну страницу)

  • JOIN-карта: где кардинальность растёт из-за дубликатов; min/max строк для INNER/LEFT/RIGHT/FULL.
  • ON vs WHERE: при LEFT фильтровать в ON, чтобы сохранить «пустые» строки; WHERE — после джойна и может «съесть» NULL.
  • Анти-кейсы: LEFTWHERE t.col IS NULL и NOT EXISTS.
  • Окна: ROW_NUMBER/RANK/DENSE_RANK; фреймы ROWS BETWEEN n PRECEDING AND CURRENT ROW.
  • Частые ловушки: NULL в условиях, COUNT(*) vs COUNT(col), COUNT(DISTINCT col); DISTINCT ≠ «всегда достаточно».
  • Паттерны: «последняя запись на сущность», «скользящее окно», «широкая витрина по месяцам».

Кому какой формат подготовки

Разные уровни подготовки требуют разных типов задач, и часто кандидаты выбирают не те форматы, из-за чего буксуют на одном и том же. Чтобы системно подтянуть SQL, обычно работает следующая логика:
Уровень Junior (100 задач)
Базовые вещи без лишней теории: агрегации, JOIN, даты, аккуратные формулировки запросов. На этом уровне важно научиться писать понятный код и уверенно разбирать свои решения.
Уровень Middle (100 задач)
Ближе к «боевой» логике: оконные функции, цепочки CTE, tricky-фильтры, EXISTS и анти-кейсы. Часто именно здесь открывается понимание того, почему запросы выглядят так, а не иначе.
Уровень Upper-Middle (50 задач)
Более сложные конструкции:
витрины, широкие таблицы, скользящие окна, комбинированные бизнес-кейсы, похожие на реальные аналитические задачи.
Внутри каждой задачи полезно иметь:
  • ответ-ориентир, чтобы понимать, какой должна быть итоговая выборка;
  • несколько подсказок — если решение застряло;
  • разбор с комментариями;
  • вопросы «от сеньора», которые помогают докрутить понимание на уровень выше.

Этот формат позволяет учиться не угадыванию синтаксиса, а постоянной тренировке мышления — тому, что и проверяют на собеседованиях.

Что можно сделать уже сегодня (план на 40 минут)

Если хочется начать подготовку без долгих раздумий, достаточно небольшого плана:
  • просмотреть мини-шпору ниже и освежить в памяти пять ключевых теоретических пробелов;
  • решить кейс A про фильтр в ON и WHERE — сначала самостоятельно, а затем свериться с разбором;
  • взять любую оконную задачу и разложить её на два шага: ранжирование → агрегирование по фрейму;
  • запланировать на завтра 30–60 минут практики: одну задачу на группы/JOIN и одну — на окна/EXISTS.
SQL это вообще не про заучивание. На собеседовании проверяют, как вы думаете, и насколько уверенно можете объяснить своё решение. Когда есть набор опор: шпора, паттерны, рука, набитая на задачах — нервозность уходит, и работать становится значительно спокойнее.
Для тех, кто готовится к собеседованиям системно, на Stepik есть практический трек с задачами разного уровня сложности — от первых шагов Junior / Middle до Upper-Middle. Формат построен так, чтобы можно было заниматься по 30–60 минут в день и постепенно собирать «мышечную память» для реальных интервью.
Если вы не уверены, с какого уровня начать, можно посмотреть программу или написать автору вопрос — он подскажет подходящий маршрут.