SQL на собеседовании — это не теория из учебника, а практические задачи на запросы к базе данных: написать JOIN, объяснить разницу WHERE и HAVING, найти вторую зарплату через оконные функции, обработать NULL. Проверяют не то, помните ли вы синтаксис, а понимаете ли вы, как СУБД реально выполняет запрос.
SQL спрашивают почти на каждом собеседовании, куда идёт тестировщик или аналитик — задачи с ответами повторяются из компании в компанию. Набор тем при этом ограничен. Из собеса в собес повторяются одни и те же восемь-девять сюжетов, и если разобраться в их механике на практике, закроется большая часть вопросов.
В этой статье — типичные типы задач с собеседований, каждая в одном формате: как звучит вопрос, рабочее решение с кодом, объяснение и отдельно — на что смотрит интервьюер. Все запросы проверены на корректность; где поведение зависит от диалекта (PostgreSQL, MySQL), это оговорено отдельно. Статья пригодится и как подготовка к собеседованию по SQL, и как справочник, к которому можно вернуться перед самим интервью.
Для примеров используем две простые таблицы. Будем считать, что они уже созданы:
SQL спрашивают почти на каждом собеседовании, куда идёт тестировщик или аналитик — задачи с ответами повторяются из компании в компанию. Набор тем при этом ограничен. Из собеса в собес повторяются одни и те же восемь-девять сюжетов, и если разобраться в их механике на практике, закроется большая часть вопросов.
В этой статье — типичные типы задач с собеседований, каждая в одном формате: как звучит вопрос, рабочее решение с кодом, объяснение и отдельно — на что смотрит интервьюер. Все запросы проверены на корректность; где поведение зависит от диалекта (PostgreSQL, MySQL), это оговорено отдельно. Статья пригодится и как подготовка к собеседованию по SQL, и как справочник, к которому можно вернуться перед самим интервью.
Для примеров используем две простые таблицы. Будем считать, что они уже созданы:
В чём разница между INNER JOIN и LEFT JOIN и почему WHERE ломает LEFT JOIN?
Как звучит на собесе: «Выведи всех сотрудников вместе с названием отдела. Сотрудники без отдела тоже должны попасть в выборку. А теперь оставь только тех, у кого отдел не “Финансы”.»
Первая часть — это LEFT JOIN, потому что нужны все строки левой таблицы, даже без пары справа:
Первая часть — это LEFT JOIN, потому что нужны все строки левой таблицы, даже без пары справа:
{$te}
INNER JOIN вернул бы только сотрудников, у которых department_id совпал с реальным отделом. Сотрудники с department_id IS NULL или с битой ссылкой выпали бы. LEFT JOIN сохраняет все строки слева и подставляет NULL в колонки справа, где пары нет.
А теперь ловушка. Кандидат хочет убрать «Финансы» и дописывает условие в WHERE:
А теперь ловушка. Кандидат хочет убрать «Финансы» и дописывает условие в WHERE:
Этот запрос молча выкидывает сотрудников без отдела. Причина: у них d.name равно NULL, а сравнение NULL <> 'Финансы' даёт не TRUE, а UNKNOWN, и строка не проходит фильтр. Фактически вы вернули INNER JOIN, хотя писали LEFT.
Есть два корректных варианта в зависимости от того, что вы хотите.
Если сотрудники без отдела должны остаться — фильтр по правой таблице переносим в условие соединения ON либо явно разрешаем NULL:
Есть два корректных варианта в зависимости от того, что вы хотите.
Если сотрудники без отдела должны остаться — фильтр по правой таблице переносим в условие соединения ON либо явно разрешаем NULL:
Важно понимать разницу между этими двумя вариантами. Условие в ON не выбрасывает сотрудника — оно лишь не даёт «приклеить» к нему строку отдела «Финансы», и в колонке department у такого сотрудника окажется NULL. Вариант с WHERE ... OR IS NULL оставляет в выборке и тех, кто без отдела, и тех, кто в любом отделе, кроме «Финансов». Какой из них правильный — зависит от формулировки задачи, и хороший ответ на собесе включает уточняющий вопрос.
На что смотрит интервьюер: понимаете ли вы, что условие на правую таблицу в WHERE нейтрализует внешнее соединение, и чувствуете ли разницу между фильтрацией в ON и в WHERE. Это один из самых частых вопросов в подборках задач по SQL для собеседований, потому что отделяет тех, кто понимает механику, от тех, кто заучил синтаксис.
На что смотрит интервьюер: понимаете ли вы, что условие на правую таблицу в WHERE нейтрализует внешнее соединение, и чувствуете ли разницу между фильтрацией в ON и в WHERE. Это один из самых частых вопросов в подборках задач по SQL для собеседований, потому что отделяет тех, кто понимает механику, от тех, кто заучил синтаксис.
В чём разница между WHERE и HAVING в SQL?
Как звучит на собесе: «Посчитай количество сотрудников по отделам и оставь только отделы, где больше трёх человек. И ещё: посчитай то же самое, но только для сотрудников с зарплатой выше 50 000.»
Ключевая мысль: WHERE фильтрует отдельные строки до того, как они собрались в группы, а HAVING фильтрует уже готовые группы по результату агрегатной функции. Поэтому условие на зарплату идёт в WHERE (это свойство строки), а условие «больше трёх человек» — в HAVING (это свойство группы).
Частая ошибка — пытаться написать агрегат в WHERE:
Частая ошибка — пытаться написать агрегат в WHERE:
Такой запрос упадёт с ошибкой, потому что на этапе WHERE групп ещё не существует, считать COUNT(*) не из чего.
Ещё один нюанс, который любят на собесах: в стандартном SQL и в PostgreSQL все неагрегированные колонки из SELECT обязаны присутствовать в GROUP BY. То есть SELECT department_id, name, COUNT(*) ... GROUP BY department_id в PostgreSQL даст ошибку, потому что name не сгруппирован. В MySQL исторически это допускалось (в режиме без ONLY_FULL_GROUP_BY выбиралось произвольное значение name), но в MySQL 5.7+ режим ONLY_FULL_GROUP_BY включён по умолчанию, и поведение совпало со стандартом. Полагаться на старое поведение MySQL не стоит.
На что смотрит интервьюер: понимаете ли вы порядок «фильтр строк → группировка → фильтр групп» и не путаете ли WHERE с HAVING. Это база, без которой не пройти даже простой блок вопросов с ответами на джуниорской позиции.
На что смотрит интервьюер: понимаете ли вы порядок «фильтр строк → группировка → фильтр групп» и не путаете ли WHERE с HAVING. Это база, без которой не пройти даже простой блок вопросов с ответами на джуниорской позиции.
Чем отличаются ROW_NUMBER, RANK и DENSE_RANK и как найти вторую зарплату в SQL?
Как звучит на собесе: «Найди вторую по величине зарплату в компании.» Иногда добавляют: «вторую по величине зарплату в каждом отделе».
Это самая популярная задача на оконные функции, и в ней зашита ловушка с дубликатами. Сначала разберём, чем отличаются три ранжирующие функции, потому что от выбора зависит ответ.
Это самая популярная задача на оконные функции, и в ней зашита ловушка с дубликатами. Сначала разберём, чем отличаются три ранжирующие функции, потому что от выбора зависит ответ.
Допустим, зарплаты такие: 100, 100, 90, 80. Функции дадут разное:
ROW_NUMBER → 1, 2, 3, 4. Сквозная нумерация, одинаковые значения получают разные номера.
RANK → 1, 1, 3, 4. Одинаковым значениям — один ранг, но следующий ранг «перепрыгивает» (после двух единиц сразу идёт 3).
DENSE_RANK → 1, 1, 2, 3. Одинаковым значениям — один ранг, нумерация без пропусков.
Теперь сам вопрос: что такое «вторая по величине зарплата»? Чаще всего подразумевают второе уникальное значение — но это первое, что стоит уточнить у интервьюера. Имеется в виду второе уникальное значение зарплаты, а не зарплата второго человека в списке. Если двое получают по 100, то «вторая зарплата» — это 90, а не вторая сотня. Поэтому корректный инструмент — DENSE_RANK:
ROW_NUMBER → 1, 2, 3, 4. Сквозная нумерация, одинаковые значения получают разные номера.
RANK → 1, 1, 3, 4. Одинаковым значениям — один ранг, но следующий ранг «перепрыгивает» (после двух единиц сразу идёт 3).
DENSE_RANK → 1, 1, 2, 3. Одинаковым значениям — один ранг, нумерация без пропусков.
Теперь сам вопрос: что такое «вторая по величине зарплата»? Чаще всего подразумевают второе уникальное значение — но это первое, что стоит уточнить у интервьюера. Имеется в виду второе уникальное значение зарплаты, а не зарплата второго человека в списке. Если двое получают по 100, то «вторая зарплата» — это 90, а не вторая сотня. Поэтому корректный инструмент — DENSE_RANK:
Если бы мы взяли ROW_NUMBER и rn = 2, то при двух зарплатах по 100 получили бы 100 — вторую сотню, а это не «вторая по величине». Именно поэтому интервьюер часто подкидывает данные с дубликатами: проверить, держите ли вы краевые случаи.
Вторая по величине зарплата в каждом отделе — добавляем PARTITION BY:
Вторая по величине зарплата в каждом отделе — добавляем PARTITION BY:
PARTITION BY перезапускает нумерацию для каждого отдела, как GROUP BY, но без схлопывания строк.
На что смотрит интервьюер: различаете ли вы три ранжирующие функции и понимаете ли, что «вторая по величине» означает второе уникальное значение. Это любимая задача с ответами на Middle-позицию. Бонусом могут спросить решение без оконных функций (через LIMIT 1 OFFSET 1 по DISTINCT salary — но оно тоже должно работать с уникальными значениями).
На что смотрит интервьюер: различаете ли вы три ранжирующие функции и понимаете ли, что «вторая по величине» означает второе уникальное значение. Это любимая задача с ответами на Middle-позицию. Бонусом могут спросить решение без оконных функций (через LIMIT 1 OFFSET 1 по DISTINCT salary — но оно тоже должно работать с уникальными значениями).
Почему = NULL не работает в SQL и чем отличаются COUNT(*) и COUNT(col)?
Как звучит на собесе: «Выведи сотрудников без отдела» и сразу следом: «В чём разница между COUNT(*) и COUNT(department_id)?»
Первая часть проверяет понимание трёхзначной логики. Сравнение чего угодно с NULL через обычные операторы возвращает не TRUE и не FALSE, а UNKNOWN. Поэтому такой запрос вернёт пусто, даже если сотрудники без отдела есть:
Первая часть проверяет понимание трёхзначной логики. Сравнение чего угодно с NULL через обычные операторы возвращает не TRUE и не FALSE, а UNKNOWN. Поэтому такой запрос вернёт пусто, даже если сотрудники без отдела есть:
Правильно — через IS NULL:
То же касается <> NULL — его заменяет IS NOT NULL.
Вторая часть — про агрегаты. COUNT(*) считает все строки, включая те, где есть NULL. COUNT(department_id) считает только строки, где department_id не NULL:
Если из десяти сотрудников у двоих department_id IS NULL, то COUNT(*) вернёт 10, а COUNT(department_id) — 8. Отсюда же следует, что COUNT(*) - COUNT(department_id) даёт число сотрудников без отдела.
Полезно знать про COUNT(DISTINCT col) — он считает уникальные ненулевые значения. И отдельно: большинство агрегатов (SUM, AVG, MAX, MIN) тоже игнорируют NULL. Это важно для AVG: среднее считается по количеству ненулевых значений, а не по общему числу строк, и подменять NULL нулём через COALESCE перед AVG — это уже другой результат.
На что смотрит интервьюер: не пишете ли вы = NULL, понимаете ли трёхзначную логику и разницу между COUNT(*) и COUNT(col). Это база, которую обязательно проверяют в любой подготовке к собеседованию по SQL.
Полезно знать про COUNT(DISTINCT col) — он считает уникальные ненулевые значения. И отдельно: большинство агрегатов (SUM, AVG, MAX, MIN) тоже игнорируют NULL. Это важно для AVG: среднее считается по количеству ненулевых значений, а не по общему числу строк, и подменять NULL нулём через COALESCE перед AVG — это уже другой результат.
На что смотрит интервьюер: не пишете ли вы = NULL, понимаете ли трёхзначную логику и разницу между COUNT(*) и COUNT(col). Это база, которую обязательно проверяют в любой подготовке к собеседованию по SQL.
Подзапрос или CTE (WITH): что выбрать в SQL?
Как звучит на собесе: «Выведи сотрудников, чья зарплата выше средней по компании. Реши через подзапрос, а потом перепиши через CTE.»
Через подзапрос в WHERE:
Через подзапрос в WHERE:
Здесь подзапрос возвращает одно число — среднюю зарплату, и мы сравниваем с ним. Это скалярный подзапрос.
Через CTE (Common Table Expression, общее табличное выражение):
Через CTE (Common Table Expression, общее табличное выражение):
Функционально оба запроса дают одно и то же. CTE выигрывает в читаемости, когда промежуточный результат используется несколько раз или когда запрос многоступенчатый: вместо вложенных друг в друга подзапросов получаются именованные «блоки», которые читаются сверху вниз. Подзапрос компактнее для одноразового простого случая.
Отдельно стоит знать про рекурсивные CTE (WITH RECURSIVE) — это то, что подзапросом не сделать в принципе. Классическая задача — обход иерархии, например дерево «сотрудник → руководитель» или категории товаров с вложенностью. На Middle-собесе про рекурсивный CTE могут спросить как про продвинутую тему.
Про производительность: распространённый миф, что CTE всегда медленнее или всегда «материализуется» (вычисляется один раз и кладётся во временную таблицу). На деле это зависит от СУБД и версии. В PostgreSQL до версии 12 CTE были оптимизационным барьером и всегда материализовались; начиная с 12-й версии планировщик может встраивать (inline) CTE, если оно не рекурсивное и используется один раз. В MySQL 8.0 CTE тоже могут как материализоваться, так и встраиваться в зависимости от запроса. Поэтому корректный ответ — «зависит от СУБД и версии», а не «CTE медленнее».
На что смотрит интервьюер: умеете ли вы писать одно и то же двумя способами, понимаете ли, когда CTE улучшает читаемость, и не повторяете ли миф про гарантированную «медленность» CTE.
Отдельно стоит знать про рекурсивные CTE (WITH RECURSIVE) — это то, что подзапросом не сделать в принципе. Классическая задача — обход иерархии, например дерево «сотрудник → руководитель» или категории товаров с вложенностью. На Middle-собесе про рекурсивный CTE могут спросить как про продвинутую тему.
Про производительность: распространённый миф, что CTE всегда медленнее или всегда «материализуется» (вычисляется один раз и кладётся во временную таблицу). На деле это зависит от СУБД и версии. В PostgreSQL до версии 12 CTE были оптимизационным барьером и всегда материализовались; начиная с 12-й версии планировщик может встраивать (inline) CTE, если оно не рекурсивное и используется один раз. В MySQL 8.0 CTE тоже могут как материализоваться, так и встраиваться в зависимости от запроса. Поэтому корректный ответ — «зависит от СУБД и версии», а не «CTE медленнее».
На что смотрит интервьюер: умеете ли вы писать одно и то же двумя способами, понимаете ли, когда CTE улучшает читаемость, и не повторяете ли миф про гарантированную «медленность» CTE.
Как найти и удалить дубликаты в SQL?
Как звучит на собесе: «В таблице есть полные дубликаты строк по name и salary. Сначала найди их, потом удали, оставив по одной записи.»
Найти дубли — группировка с HAVING COUNT(*) > 1:
Найти дубли — группировка с HAVING COUNT(*) > 1:
Этот запрос показывает, какие комбинации name + salary встречаются больше одного раза и сколько раз.
Удалить дубли, оставив по одной записи, удобнее всего через оконную функцию. Пронумеруем строки внутри каждой группы дубликатов и удалим все, кроме первой:
Здесь ROW_NUMBER присваивает 1 первой строке в каждой группе (с минимальным id), 2, 3 и так далее — остальным. Удаляем все, у кого rn > 1, то есть оставляем по одной записи на группу. Этот вариант работает, когда у строк есть уникальный ключ id.
В PostgreSQL есть более короткий способ через системный столбец ctid (физический адрес строки), если уникального ключа нет:
В PostgreSQL есть более короткий способ через системный столбец ctid (физический адрес строки), если уникального ключа нет:
В MySQL 8.0 оконные функции тоже есть, но DELETE с подзапросом по той же таблице, которую удаляем, имеет ограничения. Рабочий приём — соединить таблицу саму с собой и удалить строки с большим id:
На что смотрит интервьюер: знаете ли вы, что дубли ищутся через GROUP BY ... HAVING COUNT(*) > 1, и умеете ли удалять их, корректно оставляя одну запись, с учётом особенностей конкретной СУБД. На собесе уместно уточнить, есть ли уникальный ключ — это влияет на решение.
Почему NOT IN ломается с NULL и когда использовать NOT EXISTS?
Как звучит на собесе: «Выведи отделы, в которых нет ни одного сотрудника.» Звучит просто, но тут спрятана одна из самых коварных ловушек SQL.
Наивное решение через NOT IN:
Наивное решение через NOT IN:
Проблема: если хотя бы у одного сотрудника department_id IS NULL, подзапрос вернёт список вида (1, 2, NULL), и весь NOT IN для любой строки даст UNKNOWN. Механика такая: d.id NOT IN (1, 2, NULL) разворачивается в d.id <> 1 AND d.id <> 2 AND d.id <> NULL. Последнее сравнение — UNKNOWN, и всё выражение через AND тоже становится UNKNOWN (не TRUE). В итоге запрос вернёт ноль строк, хотя пустые отделы есть. Самое неприятное — он не падает с ошибкой, а молча отдаёт неверный результат.
Надёжное решение — NOT EXISTS, который к NULL нечувствителен:
Надёжное решение — NOT EXISTS, который к NULL нечувствителен:
NOT EXISTS проверяет сам факт наличия хотя бы одной строки и корректно работает, даже если в данных есть NULL.
Альтернатива — LEFT JOIN с проверкой IS NULL (антиджойн):
Альтернатива — LEFT JOIN с проверкой IS NULL (антиджойн):
Здесь мы соединяем отделы с сотрудниками, и у отделов без сотрудников колонки из employees будут NULL — их и отбираем.
На что смотрит интервьюер: знаете ли вы про ловушку NOT IN с NULL — это один из главных маркеров того, что человек реально работал с данными, а не только читал про SQL. В подборках вопросов с ответами на Middle эта тема почти обязательна.
На что смотрит интервьюер: знаете ли вы про ловушку NOT IN с NULL — это один из главных маркеров того, что человек реально работал с данными, а не только читал про SQL. В подборках вопросов с ответами на Middle эта тема почти обязательна.
В чём разница между DISTINCT и GROUP BY в SQL?
Как звучит на собесе: «Выведи список уникальных отделов, в которых есть сотрудники. Можно ли это сделать через DISTINCT и через GROUP BY? В чём разница?»
Оба варианта дают одинаковый результат для простого случая уникальных значений:
Оба варианта дают одинаковый результат для простого случая уникальных значений:
Для задачи «получить уникальные значения» они эквивалентны и обычно дают одинаковый план выполнения. Разница проявляется, когда нужны агрегаты. GROUP BY создан для того, чтобы вместе с группировкой считать COUNT, SUM, AVG и так далее:
Через DISTINCT посчитать количество по группам нельзя — он только убирает повторяющиеся строки и ничего не агрегирует.
Практическое правило: если нужны просто уникальные значения без вычислений — DISTINCT выразительнее и читается как «дай мне уникальное». Если нужны агрегаты по группам — GROUP BY. Использовать DISTINCT как «костыль» для случайно размножившихся строк (например, из-за неправильного JOIN) — плохой знак: обычно это значит, что не так написано соединение, и DISTINCT маскирует проблему, а не решает её.
На что смотрит интервьюер: понимаете ли вы, что DISTINCT и GROUP BY решают пересекающиеся, но разные задачи, и не используете ли DISTINCT для маскировки дублей от кривого JOIN.
Практическое правило: если нужны просто уникальные значения без вычислений — DISTINCT выразительнее и читается как «дай мне уникальное». Если нужны агрегаты по группам — GROUP BY. Использовать DISTINCT как «костыль» для случайно размножившихся строк (например, из-за неправильного JOIN) — плохой знак: обычно это значит, что не так написано соединение, и DISTINCT маскирует проблему, а не решает её.
На что смотрит интервьюер: понимаете ли вы, что DISTINCT и GROUP BY решают пересекающиеся, но разные задачи, и не используете ли DISTINCT для маскировки дублей от кривого JOIN.
В каком порядке выполняется SQL-запрос?
Как звучит на собесе: «В каком порядке на самом деле выполняется SQL-запрос?» Этот вопрос объясняет половину остальных ловушек, поэтому его стоит знать.
Запрос пишется в одном порядке, а логически выполняется в другом. Логический порядок такой:
Отсюда сразу следуют практические выводы, которые любят проверять. Почему нельзя ссылаться на алиас из SELECT в WHERE? Потому что WHERE выполняется раньше SELECT, и алиаса на этом этапе ещё не существует:
Запрос пишется в одном порядке, а логически выполняется в другом. Логический порядок такой:
- FROM (и JOIN) — определяются источники данных и склеиваются таблицы.
- WHERE — фильтруются отдельные строки.
- GROUP BY — строки собираются в группы.
- HAVING — фильтруются группы.
- SELECT — вычисляются выражения и алиасы колонок.
- ORDER BY — сортируется результат.
- LIMIT / OFFSET — отрезается нужный кусок.
Отсюда сразу следуют практические выводы, которые любят проверять. Почему нельзя ссылаться на алиас из SELECT в WHERE? Потому что WHERE выполняется раньше SELECT, и алиаса на этом этапе ещё не существует:
В WHERE нужно повторить выражение: WHERE salary * 12 > 600000. А вот в ORDER BY ссылаться на алиас можно, потому что сортировка выполняется после SELECT:
Небольшая оговорка по диалектам: и PostgreSQL, и MySQL как расширение стандарта разрешают ссылаться на алиас из SELECT в GROUP BY и ORDER BY; MySQL вдобавок допускает алиас в HAVING. А вот в WHERE алиас не виден ни в одном из них. Чтобы не зависеть от диалекта, в WHERE всегда пишите полное выражение.
На что смотрит интервьюер: видите ли вы за синтаксисом логику исполнения. Понимание порядка выполнения автоматически закрывает вопросы про WHERE/HAVING, про алиасы и про то, почему фильтр по правой таблице ломает LEFT JOIN.
На что смотрит интервьюер: видите ли вы за синтаксисом логику исполнения. Понимание порядка выполнения автоматически закрывает вопросы про WHERE/HAVING, про алиасы и про то, почему фильтр по правой таблице ломает LEFT JOIN.
Как готовиться, чтобы это закрепилось
Главная проблема подготовки к собеседованию по SQL — разрыв между «прочитал и понял» и «написал сам под давлением». На собесе вы пишете запрос вслух или в редакторе без автодополнения, а интервьюер подкидывает данные с дубликатами и NULL именно для того, чтобы поймать на разобранных выше ловушках. Поэтому единственный рабочий способ — решать руками, на реальных данных, с проверкой результата.
Потренировать ровно эти типы задач можно на SQL Arena — это тренажёр SQL от школы Quality Academy. Внутри более 800 задач, значительная часть доступна бесплатно, часть — по мотивам вопросов с собеседований в Яндекс, Т-Банк, Сбер, Ozon, VK и Авито. У каждой задачи есть AI-ментор: если застряли, он объясняет ошибку и помогает дойти до решения самому. Есть и отдельный режим собеседования, где задачи идут как на реальном интервью, с ограничением по времени и без подсказок. После прохождения уровней доступны сертификаты с QR-проверкой — их можно приложить к резюме как дополнительное подтверждение навыка.
Разберите восемь-девять сюжетов из этой статьи на тренажёре, прогоняя каждый на данных с дубликатами и NULL, — и большая часть SQL-вопросов на собесе перестанет быть для вас сюрпризом.
Короткий чек-лист перед собесом
Если вы уверенно объясняете каждый пункт и можете написать запрос, не подглядывая, — на собеседовании по SQL вас будет сложно застать врасплох.
Частые вопросы про SQL на собеседовании
Какие SQL-задачи чаще всего дают на собеседовании? Чаще всего это JOIN (особенно ловушка с WHERE после LEFT JOIN), разница WHERE и HAVING, поиск N-й по величине зарплаты через оконные функции, работа с NULL и удаление дубликатов. Эти восемь-девять сюжетов повторяются почти на любом техсобесе для QA и аналитика независимо от компании.
Спрашивают ли SQL у тестировщиков (QA), а не только у аналитиков и разработчиков? Да, SQL — стандартный вопрос на собеседовании QA-инженера, потому что тестировщику регулярно нужно проверять данные в базе напрямую: сверять результат операции, находить некорректные записи, писать запросы для баг-репорта. Вопросы по SQL для тестировщика обычно проще, чем для аналитика или backend-разработчика, но базовые темы — JOIN, GROUP BY, NULL — спрашивают у всех.
Сколько нужно готовиться к SQL-собеседованию? Если восемь-девять сюжетов из этой статьи уже разобраны на практике (не прочитаны, а решены руками), обычно хватает нескольких дней целенаправленной практики на реальных запросах. Дольше всего закрепляются ловушки — LEFT JOIN с WHERE и NOT IN с NULL, — их стоит прогнать несколько раз на разных данных.
Где брать SQL-задачи с ответами для практики? Задачи с разбором и проверкой решения есть на SQL Arena — там больше 800 задач, часть бесплатно, часть по мотивам вопросов реальных собеседований в крупных компаниях, плюс отдельный режим «Собеседование» с ограничением по времени.
Потренировать ровно эти типы задач можно на SQL Arena — это тренажёр SQL от школы Quality Academy. Внутри более 800 задач, значительная часть доступна бесплатно, часть — по мотивам вопросов с собеседований в Яндекс, Т-Банк, Сбер, Ozon, VK и Авито. У каждой задачи есть AI-ментор: если застряли, он объясняет ошибку и помогает дойти до решения самому. Есть и отдельный режим собеседования, где задачи идут как на реальном интервью, с ограничением по времени и без подсказок. После прохождения уровней доступны сертификаты с QR-проверкой — их можно приложить к резюме как дополнительное подтверждение навыка.
Разберите восемь-девять сюжетов из этой статьи на тренажёре, прогоняя каждый на данных с дубликатами и NULL, — и большая часть SQL-вопросов на собесе перестанет быть для вас сюрпризом.
Короткий чек-лист перед собесом
- LEFT JOIN + условие на правую таблицу в WHERE → превращается в INNER JOIN; фильтр уносим в ON или добавляем OR ... IS NULL.
- WHERE фильтрует строки до группировки, HAVING — группы после. Агрегат в WHERE недопустим.
- «Вторая по величине» = второе уникальное значение → DENSE_RANK, а не ROW_NUMBER.
- = NULL не работает никогда, только IS NULL. COUNT(*) считает все строки, COUNT(col) — без NULL.
- NOT IN ломается, если в подзапросе есть NULL; берите NOT EXISTS или LEFT JOIN ... IS NULL.
- Дубли ищем через GROUP BY ... HAVING COUNT(*) > 1, удаляем через ROW_NUMBER или self-join по id.
- Порядок выполнения: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. Алиас из SELECT в WHERE не виден.
Если вы уверенно объясняете каждый пункт и можете написать запрос, не подглядывая, — на собеседовании по SQL вас будет сложно застать врасплох.
Частые вопросы про SQL на собеседовании
Какие SQL-задачи чаще всего дают на собеседовании? Чаще всего это JOIN (особенно ловушка с WHERE после LEFT JOIN), разница WHERE и HAVING, поиск N-й по величине зарплаты через оконные функции, работа с NULL и удаление дубликатов. Эти восемь-девять сюжетов повторяются почти на любом техсобесе для QA и аналитика независимо от компании.
Спрашивают ли SQL у тестировщиков (QA), а не только у аналитиков и разработчиков? Да, SQL — стандартный вопрос на собеседовании QA-инженера, потому что тестировщику регулярно нужно проверять данные в базе напрямую: сверять результат операции, находить некорректные записи, писать запросы для баг-репорта. Вопросы по SQL для тестировщика обычно проще, чем для аналитика или backend-разработчика, но базовые темы — JOIN, GROUP BY, NULL — спрашивают у всех.
Сколько нужно готовиться к SQL-собеседованию? Если восемь-девять сюжетов из этой статьи уже разобраны на практике (не прочитаны, а решены руками), обычно хватает нескольких дней целенаправленной практики на реальных запросах. Дольше всего закрепляются ловушки — LEFT JOIN с WHERE и NOT IN с NULL, — их стоит прогнать несколько раз на разных данных.
Где брать SQL-задачи с ответами для практики? Задачи с разбором и проверкой решения есть на SQL Arena — там больше 800 задач, часть бесплатно, часть по мотивам вопросов реальных собеседований в крупных компаниях, плюс отдельный режим «Собеседование» с ограничением по времени.