Вопросы на собеседовании: SQL
Курс готовит к SQL-части собеседования: разбираем самые частые вопросы и задачи в формате «вопрос/задача → разбор → живой запрос». Каждый пример можно запустить прямо в браузере в SQLite-песочнице и проверить результат. Темы: порядок выполнения запроса, JOIN-ы и их каверзы, агрегация, подзапросы и оконные функции, классические задачи (N-я зарплата, дубликаты, топ-N, running total) и вопросы про производительность — индексы, N+1, нормализацию и транзакции.
Курс «Вопросы на собеседовании: SQL» состоит из 6 разделов и 27 уроков: Основы выборки, JOIN-ы, Агрегация и группировка, Подзапросы, CTE и оконные функции, Классические задачи с собеседований и Производительность и проектирование. Уроки идут по порядку — от основ к более сложным темам, в каждом есть объяснение с примерами, а в конце — вопросы для самопроверки. К урокам привязаны задачи с автоматической проверкой: прочитали тему — сразу закрепили её кодом.
Программа курса
1 Основы выборки
- Порядок выполнения SQL-запроса
Логический порядок FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY: почему алиас не виден в WHERE и зачем это знать на собеседовании.
- WHERE против HAVING
Разница между WHERE и HAVING: фильтр строк до группировки против фильтра групп после агрегации, с живым примером и типичной ошибкой.
- DISTINCT и уникальность строк
Как DISTINCT убирает дубликаты по всей строке, в чём отличие от GROUP BY и как работает COUNT(DISTINCT) — с живыми примерами.
- NULL и трёхзначная логика
Почему NULL = NULL не работает, что такое UNKNOWN и как ловушка NOT IN с NULL ломает запросы — разбор и исправление через NOT EXISTS.
- ORDER BY и сортировка
Направление сортировки, несколько ключей, сортировка по выражению и место NULL при ORDER BY — с живыми примерами в SQLite.
- Порядок выполнения SQL-запроса
2 JOIN-ы
- Виды JOIN: INNER, LEFT, RIGHT, FULL, CROSS
Чем отличаются типы соединений: INNER оставляет только пары, LEFT/RIGHT/FULL сохраняют строки без пары, CROSS даёт все комбинации.
- LEFT JOIN: условие в WHERE против ON
Каверзный вопрос: фильтр по правой таблице в WHERE превращает LEFT JOIN в INNER, а в ON — сохраняет строки без пары. Разбор на примере.
- Самосоединение (self-join)
Как соединить таблицу саму с собой: иерархия сотрудник-руководитель через LEFT JOIN и поиск пар без повторов через условие a.id < b.id.
- Анти-джойны: NOT EXISTS, NOT IN, LEFT JOIN IS NULL
Три способа найти строки без пары: NOT EXISTS, LEFT JOIN ... IS NULL и опасный NOT IN. Когда какой выбрать и почему NOT IN ломается на NULL.
- Дубликаты при джойнах
Почему JOIN размножает строки и раздувает суммы при агрегации, и как это чинить — агрегируя таблицы отдельно через подзапросы или CTE.
- Виды JOIN: INNER, LEFT, RIGHT, FULL, CROSS
3 Агрегация и группировка
- GROUP BY и агрегатные функции
Как GROUP BY делит строки на группы, а SUM/COUNT/AVG считают итог по каждой. Главное правило: в SELECT только колонки группировки или агрегаты.
- COUNT(*) против COUNT(col) против COUNT(DISTINCT)
Три COUNT и их отношение к NULL: COUNT(*) считает строки, COUNT(col) — ненулевые значения, COUNT(DISTINCT) — уникальные. Ловушка COUNT после LEFT JOIN.
- Условный подсчёт: агрегаты с CASE
Как считать несколько метрик за один проход через SUM(CASE WHEN ...), строить pivot-отчёты с GROUP BY и условные средние с NULL в CASE.
- Типичная задача: найти X по группам
Найти максимум в каждой группе и достать строку, где он достигается: GROUP BY + MAX, коррелированный подзапрос или JOIN со свёрнутой таблицей.
- GROUP BY и агрегатные функции
4 Подзапросы, CTE и оконные функции
- Подзапросы: скалярные и коррелированные
Виды подзапросов: скалярный возвращает одно значение, подзапрос в IN — список, коррелированный зависит от внешней строки и пересчитывается для каждой.
- CTE: конструкция WITH для читаемости
Что такое CTE (WITH): именованный временный результат, превращающий вложенные подзапросы в читаемый конвейер шагов с возможностью переиспользования.
- Оконные функции: ROW_NUMBER, RANK, DENSE_RANK
Три функции ранжирования и их разница при ничьей: ROW_NUMBER даёт уникальные номера, RANK пропускает места, DENSE_RANK — нет. PARTITION BY для окон по группам.
- Оконные функции: LAG, LEAD и SUM OVER
Как LAG/LEAD читают соседние строки для сравнения, а SUM OVER считает нарастающий итог и долю от общего — замена self-join и подзапросов.
- Классика: вторая по величине зарплата
Четыре способа найти вторую по величине зарплату: MAX среди меньших, DISTINCT с LIMIT/OFFSET, DENSE_RANK и коррелированный подзапрос. Учёт дубликатов.
- Подзапросы: скалярные и коррелированные
5 Классические задачи с собеседований
- Дубликаты строк: найти и удалить
Как найти повторяющиеся строки через GROUP BY + HAVING, пометить лишние копии ROW_NUMBER и удалить их, оставив по одной — с предотвращением через UNIQUE.
- Топ-N по группам
Как выбрать N лучших строк в каждой группе через ROW_NUMBER с PARTITION BY, и чем поведение RANK отличается при ничьей на границе.
- Нарастающий итог и разница соседних строк
Две частые оконные задачи: running total через SUM OVER (в т.ч. по группам с PARTITION BY) и разница с предыдущей строкой через LAG.
- Пропуски в последовательности и пользователи без заказов
Найти дыры в последовательности id через LEAD и сравнение соседей, а пользователей без единого заказа — через анти-джойн NOT EXISTS.
- Дубликаты строк: найти и удалить
6 Производительность и проектирование
- Индексы: как ускоряют и когда не работают
Зачем нужны индексы, как они заменяют полный скан быстрым поиском, чем за это платят и в каких случаях индекс не используется.
- Проблема N+1 и EXPLAIN QUERY PLAN
Что такое антипаттерн N+1 запросов и как его убирает один JOIN, и как EXPLAIN QUERY PLAN показывает, сканирует ли СУБД таблицу или ищет по индексу.
- Нормализация против денормализации
Компромисс проектирования: нормализация убирает дублирование ради целостности, денормализация дублирует данные ради скорости чтения. Когда что выбирать.
- Транзакции и ACID
Что такое транзакция (BEGIN/COMMIT/ROLLBACK) на примере перевода денег и что гарантируют свойства ACID: атомарность, согласованность, изоляция, надёжность.
- Индексы: как ускоряют и когда не работают