10 пар SQL-команд, которые путают на собесе аналитика
Содержание:
Зачем учить пары команд
На собеседовании аналитика проверяют не «знание SQL», а понимание нюансов. Большинство кандидатов уверенно пишут SELECT и JOIN, но плывут на парах команд, которые делают похожие вещи разными способами.
Эти 10 пар собраны из реальных собесов в Яндексе, Озоне, Сбере, Тинькофф, Wildberries и других продуктовых компаниях. Каждая — частый вопрос, который интервьюеры задают чтобы отличить «писал SQL раз в месяц» от «работаю с базой каждый день».
Под каждой парой — короткий разбор и ссылка на полную статью с примерами и подводными камнями.
WHERE vs HAVING
WHERE фильтрует строки до агрегации. HAVING — после GROUP BY. WHERE не работает с агрегатами (COUNT, SUM), HAVING — наоборот, без них почти бесполезен. Если фильтр не использует агрегат — пишите его в WHERE: оптимизатор отбросит лишние строки до группировки.
SELECT department, COUNT(*) AS cnt
FROM employees
WHERE salary > 100000 -- до агрегации
GROUP BY department
HAVING COUNT(*) > 5; -- после агрегацииПолный разбор WHERE vs HAVING →
LEFT JOIN vs INNER JOIN
INNER JOIN оставляет только строки, где совпадение нашлось в обеих таблицах. LEFT JOIN оставляет все строки из левой таблицы — если справа нет пары, поля заполняются NULL. Главная ловушка: WHERE на колонке правой таблицы превращает LEFT JOIN в INNER JOIN, потому что NULL не пройдёт фильтр.
Полный разбор LEFT vs INNER JOIN →
UNION vs UNION ALL
UNION объединяет результаты и убирает дубликаты (внутри делает сортировку и удаление). UNION ALL оставляет всё как есть — быстрее в разы. Если вы знаете, что дублей не будет, или они нужны для подсчёта — берите UNION ALL.
Полный разбор UNION vs UNION ALL →
EXISTS vs IN
IN проверяет вхождение значения в список. EXISTS проверяет, есть ли хотя бы одна строка в подзапросе. На больших таблицах EXISTS обычно быстрее, потому что прекращает проверку на первой найденной строке. Главная ловушка — NOT IN с NULL'ами в подзапросе всегда возвращает ложь, в отличие от NOT EXISTS.
DISTINCT vs GROUP BY
Оба убирают дубликаты. DISTINCT — простой и читается прозрачнее. GROUP BY — мощнее: позволяет применить агрегаты по группам. Производительность примерно одинаковая на современных движках, но GROUP BY чаще оптимизатор разворачивает в hash-aggregate, а DISTINCT — в sort-distinct.
Полный разбор DISTINCT vs GROUP BY →
RANK vs ROW_NUMBER vs DENSE_RANK
Все три — оконные функции для ранжирования. ROW_NUMBER присваивает уникальный номер строке (даже если значения в ORDER BY одинаковые). RANK даёт одинаковый номер дублям, но следующий номер пропускает (1, 1, 3). DENSE_RANK — одинаковый номер дублям, но без пропусков (1, 1, 2).
CTE vs подзапрос
CTE (WITH) — именованный блок, который читается сверху вниз. Подзапрос — анонимная вложенность. CTE удобнее для длинных запросов, потому что можно ссылаться на него несколько раз. Раньше CTE в Postgres материализовался (фактически создавал временную таблицу), сейчас — может встраиваться оптимизатором как обычный подзапрос. Рекурсия — только в CTE.
Полный разбор CTE vs подзапрос →
COUNT(*) vs COUNT(column)
COUNT(*) считает все строки, включая те, где все значения NULL. COUNT(column) считает только строки, где column IS NOT NULL. На собесе любят спросить: «Что вернёт COUNT(email) если у трёх пользователей email пустой?» — правильный ответ: общее количество минус 3.
Полный разбор COUNT(*) vs COUNT(column) →
DELETE vs TRUNCATE
DELETE удаляет строки построчно — медленнее, пишется в WAL, можно с WHERE, можно откатить транзакцией. TRUNCATE очищает таблицу целиком — быстрее, не пишется построчно в лог, не запускает row-level триггеры, не возвращает количество удалённых строк.
Полный разбор DELETE vs TRUNCATE →
GROUP BY vs PARTITION BY
GROUP BY сворачивает строки в группы — на выходе одна строка на группу. PARTITION BY (внутри оконных функций) разбивает данные на группы, но строки остаются — каждая строка получает агрегат своей партиции рядом. Если нужно посчитать долю строки от суммы её группы — это PARTITION BY.
-- доля заказа от выручки магазина
SELECT
store, order_id, amount,
amount * 1.0 / SUM(amount) OVER (PARTITION BY store) AS share
FROM orders;Полный разбор GROUP BY vs PARTITION BY →
Частые ошибки
1. Использовать оконные функции в WHERE. Оконки выполняются после WHERE — нужно завернуть в CTE или подзапрос.
2. Забыть про NULL'ы в NOT IN. Если в подзапросе хотя бы одно NULL — NOT IN вернёт пусто. Используйте NOT EXISTS.
3. Считать, что UNION всегда сохранит порядок. UNION может перетасовать строки при удалении дублей. Если порядок важен — внешний ORDER BY.
4. Брать COUNT(*) чтобы посчитать заполненные значения. Считаете людей с заполненным email? Только COUNT(email).
5. Применять WHERE к правой таблице после LEFT JOIN. Это превращает LEFT JOIN в INNER JOIN. Условие на правую таблицу — в ON, не в WHERE.
Связанные темы
- Шпаргалка по оконным функциям SQL
- JOIN в SQL: шпаргалка
- GROUP BY в SQL: шпаргалка
- Когортный анализ в SQL
- Как найти дубликаты в SQL
FAQ
Какие пары SQL-команд чаще всего спрашивают на собесе?
По опыту собесов в продуктовых компаниях — WHERE vs HAVING, LEFT JOIN vs INNER JOIN, UNION vs UNION ALL и оконные функции (RANK, ROW_NUMBER, DENSE_RANK). Эти четыре есть почти на каждом интервью аналитика.
Чем отличается DISTINCT от GROUP BY на больших данных?
Производительность близкая, но оптимизатор по-разному строит план. GROUP BY чаще получает hash-aggregate, DISTINCT — sort-distinct. На очень больших таблицах разница до 2× в пользу GROUP BY, но на средних — почти неразличима.
Когда EXISTS быстрее IN?
На больших подзапросах. EXISTS прерывается на первой найденной строке — оптимизатор может использовать short-circuit. IN сначала формирует полный набор значений. На маленьких списках разницы нет.
Можно ли использовать оконные функции в WHERE?
Нельзя. Оконки выполняются на этапе SELECT, после WHERE. Чтобы фильтровать по результату оконки — заворачивайте в CTE или подзапрос и фильтруйте по полю.
Что вернёт COUNT(*) если все строки имеют NULL в одной колонке?
COUNT(*) вернёт общее количество строк — он не зависит от значений. А вот COUNT(column) для этой колонки вернёт 0.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов в формате коротких квизов.