QueryGym Открыть тренажёр ENRU Пробное собеседование
Гайд по собеседованиям

Собеседование по SQL и Python: чего ждать и как готовиться

Практический гайд без воды по техническим собеседованиям, где проверяют SQL и Python для работы с базами данных. Он для аналитиков данных, BI- и продуктовых аналитиков, дата-инженеров, бэкенд-разработчиков, студентов и тех, кто меняет профессию. Каждая типичная ошибка и каждый день каждого плана ведут на задачу, которую можно решить прямо в браузере.

Ссылки на задачи открывают QueryGym. Задачи с пометкой бесплатно входят в бесплатный набор, для задач с пометкой Pro нужен Pro. Каждый план начинается с бесплатных задач, а в дальнейших днях есть задачи Pro: можно начать сегодня, а решать про Pro уже по ходу дела. Пометка Py стоит у задач по Python и базам данных.

Как проходят собеседования по SQL и Python

Компании комбинируют несколько форматов. От формата зависит, как готовиться, поэтому уточните у рекрутёра заранее: какая СУБД или диалект, можно ли запускать код и смотрит ли кто-то, как вы печатаете.

Онлайн-тест

На время (чаще всего от 45 до 90 минут), на платформе, проверка автоматическая по скрытым тестам. Никто не видит, как вы рассуждаете, поэтому всё решают правильность и крайние случаи. Читайте все примечания к задаче: подвох часто спрятан в строчке про NULL, одинаковые значения или про то, какие статусы учитывать.

Лайвкодинг в общем редакторе

От 30 до 60 минут с интервьюером, от одной до трёх задач, обычно по нарастающей. Вы печатаете и рассуждаете вслух. Иногда редактор вообще ничего не выполняет, поэтому тренируйтесь писать запрос и проверять его «глазами», а не только запуском.

Тестовое задание

Несколько часов или дней, набор данных с вопросами или небольшое приложение. Смотрят на правильность, явные допущения, читаемость кода, часто на тесты и короткое описание решения. Относитесь к README как к части ответа.

Устный разговор или доска

Без редактора: «спроектируйте схему для такого случая», «почему этот запрос медленный», «чем отличаются эти два уровня изоляции». Коротко, концептуально, и это хорошая возможность показать, что вы работали с настоящими системами.

Типичная цепочка: разговор с рекрутёром, затем онлайн-тест или технический скрининг, потом один-два технических раунда (SQL, иногда Python или кейс) и в конце разговор о команде и опыте. Процессы у компаний сильно различаются, так что считайте это картой, а не обещанием.

Чем различаются роли

Аналитик (данных, BI, продуктовый)Дата-инженерБэкенд-разработчик
Упор в SQLБизнес-вопросы: удержание, воронки, когорты, изменение месяц к месяцу, топ-N в группе, дедупликация. Оконные функции, даты и CASE встречаются постоянно.То же самое плюс соединения на больших объёмах, инкрементальные загрузки, дедупликация, медленно меняющиеся данные, чтение планов запросов.Умеренный: соединения, агрегация, подзапросы, чаще всего на схеме, которую нужно спроектировать или расширить.
Python и базы данныхКак тема про базы встречается редко. Чаще спросят про pandas, но он в этот гайд не входит.Часто: ETL-скрипты, пакетная обработка, транзакции, идемпотентные загрузки, миграции.Часто: проектирование схемы, ограничения, транзакции и изоляция, индексы, N+1, инъекции, миграции.
Что запоминаетсяВы уточняете, что значит метрика, прежде чем её считать, и проверяете число на здравый смысл.Вы думаете о повторных запусках, сбоях, объёмах и стоимости.Вы думаете о конкурентном доступе, целостности данных и поведении кода под нагрузкой.

Жёстких правил тут нет. На бэкенд-интервью могут попросить оконную функцию, а у аналитика спросить про схему. Основательно готовьте то, что нужно вашей роли, а смежное повторите вскользь.

Что оценивают интервьюеры

Почти любая оценочная карточка сводится к шести пунктам. Верный запрос, который вы не можете объяснить, оценят ниже, чем почти верный, который вы аккуратно построили и проверили вслух.

  • Правильность. Результат отвечает на вопрос, включая случаи, которые легко забыть. Верная гранулярность, верные столбцы, верный порядок.
  • Крайние случаи. NULL, одинаковые значения, дубликаты, пустые группы, границы дат, «а если у клиента нет заказов?». Назвать их до того, как спросят, один из самых сильных сигналов на уровне junior и middle.
  • Коммуникация. Вы пересказываете задачу, задаёте полезные вопросы, проговариваете план и честно говорите, в чём не уверены.
  • Структура. Вы строите ответ по шагам (CTE с понятными именами), а не пишете один вложенный запрос на 40 строк, в котором никто не разберётся.
  • Понимание производительности. Вы можете сказать, что примерно сделает база, где помог бы индекс и что изменится при данных в сто раз больше. Оптимизировать заранее не нужно.
  • Чистота кода. Единые псевдонимы, явные условия соединения, никакого SELECT * в итоговом ответе, одна мысль на один CTE.
Порядок приоритетов

Сначала верный ответ, потом такой, который вы можете объяснить, потом аккуратный, потом быстрый. Изобретательность — в последнюю очередь. Читаемый CTE всегда лучше хитрой «однострочки».

Пошаговый метод решения SQL-задачи вживую

Когда идёт время, заранее заученный порядок действий не даёт «зависнуть». Проходите эти пять шагов каждый раз, даже на лёгких задачах, пока они не станут привычкой.

  1. Уточните вопросСпрашивайте до того, как начнёте печатать. Как минимум выясните: гранулярность (одна строка — это что?), NULL (может ли в столбце быть пусто и что тогда делать?), одинаковые значения (если двое делят первое место, показывать обоих?), дубликаты (может ли одно событие встретиться дважды?), границы дат (включительно или нет, какой часовой пояс, от какого «сегодня» считать «последние 30 дней»?) и определения («активный», «выручка»: какие статусы учитывать?).
  2. Определите результатЗапишите список столбцов, гранулярность и порядок сортировки до первого SELECT. Набросайте две-три строки ожидаемого результата. Если не получается, значит, вы ещё не поняли задачу.
  3. Стройте по шагамНачните с минимальной верной основы (обычно одно соединение), посмотрите на неё, затем добавляйте по одному действию в виде CTE: фильтр, агрегация, ранжирование, финальный SELECT. Называйте каждый CTE по тому, что в нём лежит. После каждого шага спрашивайте себя: «сколько здесь должно быть строк?».
  4. Проверьте крайние случаиПрогоните крошечный пример через запрос вслух. Проверьте пустую группу, NULL, одинаковые значения, граничную дату и дубликат. Именно здесь чаще всего удаётся отыграть потерянные баллы.
  5. Обсудите производительность и альтернативыСкажите, что делает база (сканирование, соединение, сортировка), какой индекс помог бы, подошла бы ли оконная функция, самосоединение или EXISTS и что вы изменили бы при данных в сто раз больше.

Разобранный пример

Схема придумана для этого гайда. Даты хранятся текстом в формате ISO, деньги в копейках (центах).

users(user_id, name, country, signed_up_at)            -- signed_up_at: 'YYYY-MM-DD'
payments(payment_id, user_id, amount_cents, status, paid_at)
                                                      -- status: 'paid' | 'failed' | 'refunded'
                                                      -- paid_at: 'YYYY-MM-DD HH:MM:SS'
Вопрос

«Для каждой страны найдите пользователя, который потратил больше всех за первые 30 дней после регистрации. Учитывайте только успешные платежи».

1. Уточняем

  • Гранулярность: одна строка на страну. Если двое делят первое место, возвращаем обоих? Допустим, да, обоих.
  • «Первые 30 дней»: считается ли платёж ровно через 30 дней после регистрации? Допустим, нет: окно — день регистрации плюс следующие 29 дней (дни с 0-го по 29-й после регистрации), то есть «раньше, чем дата регистрации + 30 дней».
  • «Успешные»: только status = 'paid', возвращённые и неудачные не считаем.
  • Пользователи без платежей и страны, где никто не платил: не показываем.
  • Сумма: в денежных единицах, а не в копейках, с двумя знаками.

2. Определяем результат

country, user_id, spend: одна строка на страну (больше только при равенстве), сортировка по стране, затем по user_id.

3. Строим

WITH first_30 AS (                      -- платежи в окне каждого пользователя
  SELECT u.user_id, u.country, p.amount_cents
  FROM users u
  JOIN payments p ON p.user_id = u.user_id
  WHERE p.status = 'paid'
    AND date(p.paid_at) >= u.signed_up_at
    AND date(p.paid_at) <  date(u.signed_up_at, '+30 days')
),
per_user AS (                           -- одна строка на пользователя
  SELECT user_id, country, SUM(amount_cents) / 100.0 AS spend
  FROM first_30
  GROUP BY user_id, country
),
ranked AS (                             -- больше потратил, выше место; равные делят первое
  SELECT user_id, country, spend,
         RANK() OVER (PARTITION BY country ORDER BY spend DESC) AS rnk
  FROM per_user
)
SELECT country, user_id, printf('%.2f', spend) AS spend
FROM ranked
WHERE rnk = 1
ORDER BY country, user_id;

Каждый CTE отвечает на один вопрос: какие платежи учитываем, сколько потратил каждый пользователь, кто первый в своей стране. Здесь синтаксис SQLite; в PostgreSQL границу окна записали бы как p.paid_at < u.signed_up_at + INTERVAL '30 days'. printf('%.2f', spend) печатает ровно два знака после запятой (текстом); в PostgreSQL используйте ROUND(spend, 2).

4. Проверяем крайние случаи

На горстке придуманных строк (Ана и Бо из KZ, Си и Ди из DE, Эд из FR. Ана зарегистрировалась 5 января: 30.00 в день регистрации, 20.00 на 29-й день (3 февраля) и 99.00 на 30-й день (4 февраля); Бо: один платёж 45.00 и один возвращённый на 50.00; Си и Ди по 40.00; у Эда платежей нет) запрос вернёт:

countryuser_idspend
DE340.00
DE440.00
KZ150.00

Платёж Аны на 30-й день верно не попал в сумму (50.00, а не 149.00), возврат Бо проигнорирован, равенство в DE показало обоих, а страны FR в результате нет. Проговаривайте каждую такую проверку вслух.

5. Обсуждаем

  • Равенство: RANK оставляет обоих. Если бизнесу нужен ровно один, переходите на ROW_NUMBER() с ORDER BY spend DESC, user_id, чтобы результат был детерминированным.
  • Производительность: индекс payments(user_id, paid_at) поддерживает соединение, а фильтр по дате сможет им воспользоваться, только если сравнивать paid_at «как есть»: p.paid_at >= u.signed_up_at AND p.paid_at < date(u.signed_up_at, '+30 days'), без функции вокруг столбца, как в запросе выше. На больших данных я написал бы именно так.
  • Альтернатива: обойтись без ранжирования и сравнить сумму с MAX(spend) по стране. Ответ тот же, но оконный вариант легко превращается в «топ-3 по каждой стране».

Python и базы данных: темы и как на них отвечать

Эти темы встречаются на собеседованиях бэкенд-разработчиков и дата-инженеров, обычно вперемешку: «напишите» и «объясните». Ниже у каждой темы есть: что нужно знать, как об этом говорить и на чём потренироваться. Примеры на sqlite3 из стандартной библиотеки, как и в направлении Python в QueryGym.

Проектирование схемы и компромиссы нормализации

Начните с сущностей и связей между ними, дайте каждой таблице ключ и вынесите повторяющиеся факты в отдельные таблицы, чтобы каждый факт жил в одном месте. Нормализация (примерно до третьей нормальной формы) защищает от аномалий обновления. Денормализация — осознанный компромисс: быстрее чтение или проще отчётность в обмен на необходимость синхронизировать копии.

Как об этом говорить
  • Сначала назовите сущности и связи «один ко многим» и «многие ко многим», потом рисуйте таблицы. Для связи «многие ко многим» нужна таблица-связка.
  • Покажите, что понимаете, когда копия оправдана: order_items.unit_price хранит цену на момент продажи. Это история, а не дублирование.
  • Денормализуя, скажите, что поддерживает согласованность (триггер, задача, представление) и как это может сломаться.

Потренируйтесь: Спроектируйте таблицы пользователей и заказов Py бесплатно, Студенты, курсы и записи на курсы Py бесплатно, Нормализуйте плоскую таблицу заказов Py Pro

Ограничения (constraints)

Первичные и внешние ключи, UNIQUE, NOT NULL и CHECK позволяют базе отвергать плохие данные. Код приложения этого не гарантирует, когда пишущих клиентов несколько. В SQLite внешние ключи выключены, пока вы не выполните PRAGMA foreign_keys = ON в каждом соединении. Это классическая ловушка, и её полезно упомянуть.

Как об этом говорить
  • «Инварианты я бы закрепил в базе, а в приложении добавил бы проверки для понятных сообщений об ошибках».
  • При мягком удалении обычный UNIQUE(email) мешает зарегистрироваться заново, а частичный уникальный индекс (WHERE deleted_at IS NULL) это позволяет.
  • Скажите, что происходит при удалении: RESTRICT, CASCADE или SET NULL, и почему.

Потренируйтесь: Мягкое удаление аккаунтов с повторным использованием email Py Pro, История цен, которую ведёт триггер Py Pro

Транзакции, ACID и изоляция

Транзакция делает группу операций атомарной: выполняются либо все, либо ни одна. ACID — это атомарность, согласованность, изоляция и долговечность. Уровни изоляции — компромисс между безопасностью и параллелизмом: слабые допускают грязное чтение, неповторяющееся чтение, фантомы и потерянные обновления, а serializable ведёт себя так, будто транзакции идут по одной. По умолчанию в разных базах разное (в PostgreSQL это read committed), а SQLite выстраивает пишущие транзакции в очередь.

Как об этом говорить
  • Приведите конкретную гонку: два списания прочитали один и тот же баланс, и оба прошли. Затем решение: один UPDATE ... WHERE balance >= :amount с проверкой числа изменённых строк, блокировка строки или столбец версии для оптимистической блокировки.
  • В sqlite3 конструкция with conn: фиксирует транзакцию при успехе и откатывает при исключении, но в режиме по умолчанию транзакция открывается только перед INSERT / UPDATE / DELETE: CREATE, ALTER и PRAGMA выполняются в автокоммите. Открывайте транзакцию явно (conn.execute("BEGIN") или sqlite3.connect(..., autocommit=False) в Python 3.12+) и помните, что executescript() сначала делает COMMIT. Упомяните точки сохранения (savepoint) для частичного отката внутри большой пачки.
  • Для платежей и импортов добавьте идемпотентность: уникальный ключ запроса, чтобы повтор не применился дважды.

Потренируйтесь: Атомарный перевод денег Py бесплатно, Идемпотентная запись платежа Py Pro, Пакетный импорт с точками сохранения Py Pro

Индексы и EXPLAIN QUERY PLAN

Индекс — отсортированная структура (обычно B-дерево), которая позволяет находить строки без полного прохода по таблице. Чтение ускоряется, запись замедляется, место расходуется. В составном индексе столбцы с равенством ставят первыми, столбец с диапазоном последним. Функция над столбцом (WHERE date(created_at) = ...) или ведущий символ подстановки (LIKE '%abc'), как правило, мешают использованию индекса.

Как об этом говорить
  • Сначала выполните EXPLAIN QUERY PLAN и прочитайте результат: SCAN означает полный проход, SEARCH ... USING INDEX означает, что индекс используется.
  • Рассуждайте об избирательности: индекс по столбцу с двумя разными значениями почти не помогает.
  • Предложите покрывающий индекс, если частому запросу нужны всего несколько столбцов, и скажите, чем за это платит запись.

Потренируйтесь: Индексы для горячих запросов Py бесплатно, Постраничный вывод заказов по курсору Py Pro

Проблема N+1

N+1 — это один запрос за списком и ещё по запросу на каждую строку за связанными данными: 101 обращение к базе на 100 клиентов. На маленькой тестовой базе это не заметно, а в продакшене больно.

Как об этом говорить
  • Обнаруживается подсчётом запросов на один вызов или чтением лога запросов.
  • Лечится одним соединением или двумя запросами, где второй берёт всю пачку через WHERE id IN (...). В ORM это жадная загрузка (selectinload и joinedload в SQLAlchemy, select_related и prefetch_related в Django).
  • Упомяните компромисс: соединение по связи «один ко многим» дублирует родительскую строку, и иногда пакетный запрос оказывается лучше.

Потренируйтесь: Исправьте N+1 в отчёте по клиентам Py Pro, Сотрудники в виде словарей Py Pro

SQL-инъекции и параметризованные запросы

Если подставлять пользовательский ввод в строку с SQL, ввод может изменить сам запрос. Решение — параметры: текст запроса и значения передаются отдельно (? в sqlite3, %s в psycopg), поэтому значения никогда не становятся кодом.

# неправильно: ввод становится частью SQL
conn.execute(f"SELECT * FROM products WHERE name LIKE '%{term}%'")

# правильно: значение передаётся отдельно
conn.execute("SELECT * FROM products WHERE name LIKE ?", (f"%{term}%",))
Как об этом говорить
  • Имена таблиц и столбцов параметрами не передаются; если они приходят от пользователя, сверяйте их со списком разрешённых.
  • Экранируйте %, _ и сам символ экранирования с помощью ESCAPE (LIKE ? ESCAPE '\'), если текст пользователя нужно искать в LIKE буквально.
  • Параметры ещё и помогают базе переиспользовать планы запросов, так что это стандарт даже для доверенного ввода.

Потренируйтесь: Безопасный поиск товаров Py бесплатно, Импорт клиентов: всё или ничего Py Pro

Миграции

Миграция — это версионируемое упорядоченное изменение схемы. Раннер хранит, какие версии уже применены (в SQLite для этого удобен PRAGMA user_version), и применяет остальные по порядку.

Как об этом говорить
  • Никогда не правьте уже выпущенную миграцию; добавляйте новую.
  • Выполняйте миграцию в транзакции там, где DDL транзакционный (SQLite и PostgreSQL — да, MySQL — нет), чтобы при сбое база осталась нетронутой. В режиме sqlite3 по умолчанию миграция, где смешаны DDL и DML, сама по себе не атомарна (DDL выполняется в автокоммите), поэтому открывайте транзакцию явно (BEGIN ... COMMIT, но не через executescript(), который сначала делает COMMIT), проверяйте PRAGMA user_version внутри неё и выставляйте новую версию до фиксации.
  • Для изменений без простоя используйте схему «расширить и сжать»: добавить новый столбец, писать в оба, заполнить старые строки пачками, переключить чтение, затем удалить старый столбец.

Потренируйтесь: Раннер миграций на PRAGMA user_version Py Pro, Нормализуйте плоскую таблицу заказов Py Pro, Репозиторий подписок Py Pro

13 типичных ошибок (и задачи, на которых их отрабатывают)

Из-за этих ошибок люди теряют баллы снова и снова. У каждой есть пример и задачи, где она реально срабатывает. Держите список как чек-лист перед отправкой ответа.

1Сравнение с NULL через =

NULL = NULL не истина, а «неизвестно», и строка отбрасывается. WHERE end_date = NULL не вернёт ничего, а status <> 'x' молча отбросит строки, где status равен NULL.

WHERE end_date IS NULL
WHERE status IS DISTINCT FROM 'x'   -- стандартный SQL (PostgreSQL, SQLite 3.39+); в старом SQLite: status IS NOT 'x'

Отработайте: Проекты без даты окончания Pro, Каждый сотрудник и его руководитель (если есть) Pro

2NOT IN, когда в подзапросе есть NULL

x NOT IN (1, 2, NULL) никогда не истина, поэтому один NULL в подзапросе опустошает весь результат. Лучше использовать NOT EXISTS или отфильтровать NULL.

WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id)

Отработайте: Пользователи без активной подписки Pro, Пользователи, которые ни разу не подписывались Pro

3COUNT(*) вместо COUNT(столбец)

После LEFT JOIN родитель без пары всё равно даёт одну строку, поэтому COUNT(*) покажет 1 там, где на самом деле 0. COUNT(child.id) пропускает NULL и вернёт 0.

SELECT d.name, COUNT(e.employee_id) AS headcount   -- не COUNT(*)
FROM departments d LEFT JOIN employees e ON e.department_id = d.department_id
GROUP BY d.department_id, d.name;

Отработайте: Численность отделов бесплатно, Численность по отделам (включая пустые) бесплатно

4WHERE или HAVING

WHERE фильтрует строки до группировки и не видит агрегатов, HAVING фильтрует группы после. Если условие можно поставить в WHERE, ставьте: и понятнее, и дешевле.

SELECT customer_id, COUNT(*) AS n
FROM orders
WHERE status = 'completed'      -- строки
GROUP BY customer_id
HAVING COUNT(*) > 3;           -- группы

Отработайте: Постоянные покупатели бесплатно, Самые активные пользователи для бета-программы бесплатно

5LEFT JOIN незаметно превратился в INNER JOIN

Условие в WHERE по правой таблице убирает строки без пары (где её столбцы равны NULL), и LEFT JOIN перестаёт работать как левый. Условия про правую таблицу кладите в ON.

-- теряем тарифы без активной подписки
FROM plans p LEFT JOIN subscriptions s ON s.plan_id = p.plan_id
WHERE s.status = 'active'

-- сохраняем все тарифы
FROM plans p LEFT JOIN subscriptions s ON s.plan_id = p.plan_id AND s.status = 'active'

Отработайте: Использование экспорта по тарифам Pro, Клиенты без выполненных заказов Pro

6Размножение строк при соединении

Если присоединить к одному родителю две таблицы «один ко многим», строки перемножатся (3 подписки на 5 событий — это 15 строк), и SUM с COUNT окажутся завышенными. Сначала агрегируйте каждую сторону в своём CTE или считайте уникальные ключи. DISTINCT в верхнем SELECT чаще маскирует проблему, чем решает её.

Отработайте: Товары, которые покупают вместе Pro, Использование экспорта по тарифам Pro

7Целочисленное деление

В SQLite и PostgreSQL 3 / 2 равно 1, поэтому completed / total получится 0 (или 1), а 100 * completed / total обрежется до целого числа. Заставьте базу считать с дробной частью, умножив на 100.0 первым, и защититесь от деления на ноль через NULLIF.

ROUND(100.0 * completed / NULLIF(total, 0), 1)

Отработайте: Доля выполненных заказов по клиентам Pro, Воронка «регистрация → оплата» по странам Pro

8Равные значения в ранжировании

ROW_NUMBER раскладывает равные значения произвольно, RANK даёт им один ранг и пропускает следующий, DENSE_RANK не пропускает. «Топ-3» может значить три строки или три ранга, уточняйте. А LIMIT 1 при равном максимуме молча прячет пользователя.

Отработайте: Рейтинг клиентов по выполненным заказам: RANK и DENSE_RANK Pro, Самый высокооплачиваемый подчинённый у каждого руководителя Pro

9ORDER BY без уникального ключа при LIMIT и OFFSET

Если ключ сортировки не уникален, база вправе отдавать равные строки в любом порядке: страница 3 может пересечься со страницей 2, а топ-5 меняться между запусками. Заканчивайте ORDER BY уникальным столбцом.

ORDER BY order_date DESC, order_id DESC LIMIT 10 OFFSET 20

Отработайте: Третья страница списка заказов бесплатно, Пять самых дорогих товаров бесплатно

10Границы диапазонов дат: включительно или нет

BETWEEN включает оба конца. Для timestamp условие paid_at BETWEEN '2024-12-01' AND '2024-12-31' теряет всё, что произошло после полуночи 31-го (в SQLite с текстовыми timestamp — весь день целиком). Используйте полуоткрытый диапазон: начало включительно, начало следующего периода исключительно.

WHERE paid_at >= '2024-12-01' AND paid_at < '2025-01-01'

Отработайте: Регистрации в первом квартале бесплатно, Новички за последние двенадцать месяцев Pro

11Подсчёт не тех строк

Выручка обычно означает только завершённые заказы; у «активного» часто есть точное определение; отменённые и возвращённые строки могут как учитываться, так и нет. Пропустив уточняющий вопрос, вы получите уверенно неверное число. Выносите правило в именованный CTE, чтобы оно было на виду.

Отработайте: Топ-5 клиентов по выручке от выполненных заказов Pro, Реализованная выручка по месяцам Pro

12LIMIT вместо «топ-N в каждой группе»

LIMIT 2 вернёт две строки всего, а не по две на категорию. Ранжируйте внутри группы оконной функцией, а отбирайте во внешнем запросе (результат окна нельзя использовать в том же WHERE).

WITH r AS (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY revenue DESC, product_id) AS rn
  FROM product_revenue)
SELECT * FROM r WHERE rn <= 2;

Отработайте: Топ-2 товара по выручке в каждой категории Pro, Последний выполненный заказ каждого клиента бесплатно

13Агрегаты молча пропускают NULL

AVG, SUM и COUNT(столбец) игнорируют NULL, поэтому сотрудники без назначений исчезают из среднего, куда они должны были входить. Решите, значит ли «нет данных» ноль, и скажите это явно через COALESCE. Среднее из средних, кстати, тоже не равно общему среднему.

Отработайте: Средняя загрузка проектами по отделам Pro, Пользователи по статусу подписки Pro

Как общаться на интервью: думайте вслух

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

Рассуждайте вслух

  • Пересказывайте задачу своими словами: «То есть мне нужна одна строка на страну, лучший по тратам за первые 30 дней. Верно?»
  • Объявляйте план до кода: «Сначала отфильтрую платежи по окну каждого пользователя, потом посчитаю сумму на пользователя, потом проранжирую внутри страны».
  • Проговаривайте решения, а не нажатия клавиш: «Здесь нужен LEFT JOIN, потому что клиенты без заказов тоже должны попасть в результат».
  • Говорите о том, в чём не уверены, а не прячьте это: «Не помню, есть ли эта функция в таком диалекте; напишу так, как думаю, а вы поправите, если что».

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

  • Что такое одна строка результата? Какая гранулярность у каждой таблицы?
  • Может ли этот столбец быть NULL? Бывают ли пользователи без заказов?
  • Если значения равны, вернуть обоих или выбрать одного?
  • Бывают ли дубликаты строк или этот ключ уникален?
  • Диапазоны дат включительно? Какой часовой пояс? От какого «сегодня» считаем?
  • Какие статусы учитываются в выручке или в «активных»?
  • Насколько большие таблицы и нужно ли думать о производительности?
  • Для какого диалекта пишу и можно ли запускать запрос?

Если застряли

  1. Скажите, где вы находитесь: «Понимаю, что нужен последний заказ каждого клиента, и выбираю между оконной функцией и коррелированным подзапросом».
  2. Уменьшите задачу: решите для одного клиента, потом обобщите.
  3. Напишите ту часть, в которой уверены, и посмотрите на её результат.
  4. Разберите крошечный пример вручную: какую строку вы бы выбрали и почему.
  5. Если минуту-две нет движения, попросите подсказку: «Можно уточнить, мне стоит смотреть в сторону оконных функций?» Спросить рано дешевле, чем молчать.

Проверяйте запрос вслух

Не запускайте и не говорите просто «вроде верно». Возьмите три-четыре строки и проведите их через запрос: «Ана зарегистрировалась 5 января, платёж 6 января попадает в окно, а платёж 4 февраля — тридцатый день, значит, он не входит». И каждый раз называйте, какой именно крайний случай проверяете.

Как принимать подсказки

  • Подсказка — не провал, а помощь интервьюера, чтобы вы смогли показать, на что способны. Поблагодарите и используйте её.
  • Перескажите подсказку своими словами, затем примените. Так видно, что вы поняли, а не скопировали.
  • Не спорьте из обороны. Если не согласны, спокойно объясните почему, с примером.
  • Если ошиблись, скажите, что сделали бы иначе, и исправьте. Умение выправить ситуацию — сильный сигнал.

Планы подготовки: 7, 14 или 30 дней

Выберите план по времени, которое у вас есть, а не по амбициям. День — это один-два часа: короткое чтение, от двух до четырёх задач от простых к сложным и разбор своих ошибок. Задачи, для которых нужен Pro, помечены, а первые дни каждого плана состоят из бесплатных задач, чтобы можно было начать сразу. Контрольные точки с пробным собеседованием выделены рамкой: это честная проверка прогресса, поэтому проходите их на время и не подглядывайте в решения. Пробные собеседования уровней Junior и Middle работают на бесплатных задачах; для Senior (1 средняя и 2 сложные задачи) нужен Pro. Бесплатные задачи средней сложности планы приберегают до первого пробного собеседования, чтобы в нём были «свежие» задачи.

Как пользоваться любым планом
  • Над каждой задачей думайте 10–15 минут, прежде чем просить подсказку. Потом идите по ступенчатым подсказкам и только затем к разбору решения (разборы открываются, когда вы решили задачу, или с Pro).
  • Ведите «журнал ошибок»: по строке на каждый промах (например, «забыл уникальный ключ в сортировке»). Перед каждым пробным собеседованием перечитывайте его.
  • Если день не задался, повторите его, а не двигайтесь дальше. Планы ломаются именно из-за пропущенных слабых тем.
  • Не пропускайте дни с контрольными точками: пробное собеседование под давлением учит тому, чего не даёт спокойная практика.
  • Трезво смотрите на то, что бесплатно: в бесплатный набор входят фильтрация, сортировка и постраничный вывод, задача на строки, агрегация и три задачи средней сложности (LEFT JOIN, подзапрос и оконная функция), а в Python — проектирование схемы, транзакция, безопасные запросы и план индексов. Большинство соединений, подзапросов, CTE, оконных функций и задач на даты в Pro, поэтому для поздних дней каждого плана он понадобится.

Экспресс-план на 7 дней

Если собеседование на следующей неделе, а базовый SELECT вы уже знаете. Ядро в фиксированном порядке: фильтрация, агрегация, соединения, CTE, окна и день на Python и базы данных, плюс две контрольные точки с пробным собеседованием. В днях 1–2 и в день Python (день 6) только бесплатные задачи. В остальных днях есть задачи Pro.

День 1Фильтрация, NULL и даты

Сначала прочитайте ошибки 1, 9 и 10.

День 2Агрегация: GROUP BY и HAVING

Прочитайте ошибку 4.

День 3Соединения и анти-соединения

Прочитайте ошибки 2, 3, 5 и 6. Контрольная точка: пройдите первое короткое пробное собеседование, чтобы понять, с чего вы начинаете. Если будет тяжело, это нормально: для того оно и нужно.

День 6Python и базы данных

Прочитайте раздел про Python и базы данных и потренируйтесь проговаривать вслух каждый блок «Как об этом говорить».

День 7День пробного собеседования и разбор

Контрольная точка: пройдите полное пробное собеседование на время, затем попросите ИИ-наставника разобрать результат. Исправьте два главных пункта из журнала ошибок, а двумя задачами выше займитесь, только если останутся силы. Закончите чек-листом.

План на 14 дней

Сбалансированный план. По дню на тему, два дня на Python и базы данных, день сложных задач и три контрольные точки. Подойдёт, если можете уделять час-два в день. В днях 1–3 только бесплатные задачи. В дальнейших днях есть задачи Pro.

День 2NULL, даты и устойчивая сортировка

Прочитайте ошибки 1, 9 и 10.

День 3Агрегация, часть 1

Прочитайте ошибки 3 и 4. Контрольная точка: пройдите первое короткое пробное собеседование и запишите, с чего вы начинаете.

День 11Python и базы данных, часть 1: схема и доступ к данным

Прочитайте темы про схему, ограничения и инъекции.

День 12Python и базы данных, часть 2: транзакции и производительность

Прочитайте темы про транзакции, индексы и N+1.

День 13Сложный SQL и повторение

Затем перерешайте всё из журнала ошибок, что всё ещё не получается.

День 14День пробных собеседований

Контрольная точка: пройдите два пробных собеседования (одно с упором на SQL, другое смешанное), получите разбор от ИИ-наставника и закончите чек-листом. Две задачи выше нужны, только если остались силы.

План на 30 дней

Подробный план для тех, кто меняет профессию или начинает почти с нуля. По теме в день в течение четырёх недель, первое пробное собеседование на 3-й день для исходного уровня, контрольная точка в конце каждой недели и лёгкий последний день. В днях 1–4 только бесплатные задачи. В дальнейших днях есть задачи Pro.

Неделя 1: основа

День 1Фильтрация
День 2Даты, диапазоны и строки

Прочитайте ошибку 10.

День 3Сортировка и постраничный вывод

Прочитайте ошибку 9. Контрольная точка: пройдите первое короткое пробное собеседование, чтобы зафиксировать исходный уровень. Если будет тяжело, это нормально: для того оно и нужно.

День 4Агрегация, часть 1

Прочитайте ошибки 3 и 4.

День 7Повторение и второе пробное собеседование

Контрольная точка: пройдите второе пробное собеседование и сравните его с исходным уровнем с третьего дня, затем перерешайте две задачи выше, если они давались тяжело, и обновите журнал ошибок.

Неделя 2: соединения и подзапросы

День 9Соединения, часть 2: выручка и размножение строк

Прочитайте ошибки 5, 6 и 11.

День 14Повторение и третье пробное собеседование

Контрольная точка: пройдите пробное собеседование и сравните с предыдущими. Перерешайте две задачи выше и обновите журнал ошибок.

Неделя 3: даты, окна и аналитика

День 21Повторение и четвёртое пробное собеседование

Контрольная точка: пройдите пробное собеседование на самом сложном уровне, который вам по силам (Senior доступен с Pro), затем попросите ИИ-наставника разобрать его. Перерешайте две задачи выше.

Неделя 4: Python и базы данных, сложный SQL, пробные собеседования

День 24Python и базы данных: безопасный доступ к данным
День 25Python и базы данных: производительность
День 29День пробных собеседований

Контрольная точка: пройдите два пробных собеседования (по SQL и смешанное) и получите разбор от ИИ-наставника. Выберите три самые слабые темы из журнала ошибок и подтяните их. Две задачи выше нужны, только если остались силы.

День 30Лёгкий день: разминка и отдых

Разомнитесь двумя задачами выше, перечитайте чек-лист и метод решения и закончите пораньше. Отдых тоже часть плана.

Чек-лист: накануне и в день интервью

Накануне

  • Подтвердите у рекрутёра формат, время, инструменты и диалект.
  • Проверьте оборудование: ссылку, камеру, микрофон, интернет, заряженный ноутбук и запасной способ подключиться.
  • Перечитайте журнал ошибок и тринадцать типичных ошибок выше.
  • Перечитайте пять шагов метода и один раз проговорите их вслух.
  • Решите одну-две простые задачи для разминки. Не затевайте марафон.
  • Освежите синтаксис, который вечно забывается: рамки окон, функции дат нужного диалекта, строковые функции.
  • Подготовьте два-три вопроса интервьюеру и короткую историю о задаче с данными, которую вы решали.
  • Выспитесь. Отдохнувшая голова выигрывает у ночной зубрёжки.

В день интервью

  • Поешьте, возьмите воды, приготовьте лист бумаги и ручку, чтобы набросать таблицы.
  • Разомнитесь 15 минут на простом запросе, чтобы руки вспомнили синтаксис.
  • Каждую задачу начинайте с уточняющих вопросов и записывайте столбцы результата до первого SELECT.
  • Стройте по шагам, рассуждайте вслух и проверяйте на маленьком примере, прежде чем сказать «готово».
  • Если вы застряли дольше пары минут, скажите об этом и уменьшите задачу.
  • Следите за временем: сначала верный базовый ответ, потом улучшения.
  • Оставьте несколько минут, чтобы перечитать условие и сверить с ним результат.
  • В конце задайте свои вопросы и уточните, какие дальше шаги.

Тренировки с ИИ-наставником

ИИ-наставник QueryGym лучше всего работает как терпеливый учитель и как интервьюер для пробных собеседований. Он видит условие задачи, ваш код и результаты проверки, так что ничего вставлять не нужно. Он отвечает по-русски или по-английски, а это удобно, если собеседовать вас будут не на том языке, на котором вы учитесь. Чтобы им пользоваться, нужно войти в аккаунт, а ещё действует лимит: 20 сообщений в день, поэтому формулируйте вопросы точно.

Просите подсказки, а не ответы

  • Сначала попробуйте сами: 10–15 минут в одиночку, потом спрашивайте.
  • Спрашивайте конкретно: «Почему здесь 12 строк, хотя я ожидаю 9?» лучше, чем «помоги».
  • Просите минимальную подсказку: «Дай намёк без запроса» или «Что мне проверить в первую очередь?». Подсказки ступенчатые, просите следующую, только когда она нужна.
  • Проверяйте решение кнопкой проверки, а не чатом. Когда проверка пройдена, спросите «почему это работает?» и объясните ответ обратно своими словами.

Два действия в роли интервьюера

«Проведи со мной собеседование по этой задаче»

Наставник играет роль интервьюера: задаёт уточняющие вопросы, спрашивает про ваш подход, крайние случаи и сложность, задаёт дополнительные вопросы. Отвечайте полными фразами, как отвечали бы вслух.

«Разбери как интервьюер»

Решив задачу, попросите оценку так, как её дал бы интервьюер: правильность, пропущенные крайние случаи, читаемость, производительность и то, о чём он спросил бы дальше.

После пробного собеседования

Когда пробное собеседование закончится, вы получите отчёт и сможете попросить наставника разобрать сессию: что получилось, где вы потеряли время или баллы и что тренировать дальше. Перенесите выводы в журнал ошибок и в следующий день плана.

Сохраняйте здоровый скепсис

Наставник может ошибаться, и это учебное пособие, а не гарантия оффера. За правильность отвечает проверка решения. Если объяснение непонятно, так и скажите и спросите ещё раз.

Готовы попробовать на время?

Пройдите бесплатное пробное собеседование, а потом вернитесь к плану, который подходит вашей неделе.