Вопросы по SQL на собеседовании аналитика

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Зачем это знать

SQL — ядро большинства собесов аналитика. Без прохода SQL-секции дальше не пустят. Подготовиться к частым вопросам = значительно поднять шансы.

Ниже — топ-30 вопросов с коротким разбором. Если видите каждый впервые — значит вы junior. Если ответите на все без подготовки — уверенный middle.

Часть 1: базовые

1. Разница WHERE и HAVING

  • WHERE: фильтрует до группировки (строки)
  • HAVING: фильтрует после группировки (агрегаты)

2. Разница JOIN'ов

  • INNER: только совпадения
  • LEFT: все из левой + совпавшие
  • RIGHT: наоборот (редко)
  • FULL OUTER: все из обеих
  • CROSS: декартово произведение

3. Что быстрее: COUNT(*) или COUNT(1)?

Одинаково в современных СУБД. COUNT(*) — стандарт.

4. COUNT(*) vs COUNT(column)?

COUNT(*) включает NULL. COUNT(column) — только non-NULL.

5. DISTINCT vs GROUP BY?

Функционально часто эквивалентны. GROUP BY удобнее при агрегациях.

6. Порядок выполнения SQL

FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.

7. Что такое NULL?

«Неизвестное» значение. NULL = NULL → UNKNOWN (не TRUE).

8. Как работает NULL в сравнении?

col = NULL всегда FALSE/UNKNOWN. Используйте IS NULL.

9. UNION vs UNION ALL

  • UNION: удаляет дубликаты (медленнее)
  • UNION ALL: оставляет (быстрее)

10. Что такое индекс?

Структура для быстрого поиска. B-tree на колонке → O(log N) instead of O(N).

Часть 2: средний уровень

11. Оконные функции

Считают над «окном» строк без свёртки. ROW_NUMBER, RANK, SUM OVER, LAG.

12. ROW_NUMBER vs RANK vs DENSE_RANK

  • ROW_NUMBER: уникальные номера
  • RANK: повторы при связке, пропуски следующих
  • DENSE_RANK: повторы без пропусков

13. PARTITION BY vs GROUP BY

PARTITION BY — в оконной функции, не сворачивает строки. GROUP BY — сворачивает.

14. CTE (WITH)

Именованный подзапрос для читаемости и переиспользования.

15. Когда CTE vs подзапрос?

CTE — для читаемости, рекурсии, переиспользования. Подзапрос — для одноразового.

16. Топ-N в каждой группе

SELECT * FROM (
    SELECT *, DENSE_RANK() OVER (PARTITION BY cat ORDER BY val DESC) AS rnk
    FROM t
) WHERE rnk <= 3;

17. Вторая максимальная зарплата

SELECT MAX(salary) FROM employees WHERE salary < (SELECT MAX(salary) FROM employees);

Или DENSE_RANK = 2.

18. Дубликаты по email

SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;

19. Running total

SELECT day, SUM(rev) OVER (ORDER BY day) FROM daily;

20. LAG для DoD

SELECT day, rev, rev - LAG(rev) OVER (ORDER BY day) AS dod FROM daily;

Часть 3: продвинутый

21. EXISTS vs IN

EXISTS — коррелированный, safer с NULL. IN — простой список.

22. NOT IN с NULL

Если в подзапросе есть NULL → результат пустой. Используйте NOT EXISTS.

23. Median в SQL

SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY col) FROM t;

24. PIVOT в SQL

Через CASE WHEN + GROUP BY. Нативный PIVOT только в MSSQL / BigQuery.

25. Рекурсивный CTE

WITH RECURSIVE t AS (
    SELECT ... -- base CASE
    UNION ALL
    SELECT ... -- RECURSIVE CASE
)

Для hierarchies.

26. Оптимизация медленного запроса

EXPLAIN → добавить индекс → избавиться от function(col) в WHERE → переписать с JOIN вместо подзапросов.

27. EXPLAIN plan

Seq Scan — без индекса. Index Scan — с индексом. Cost = estimated time.

28. Когда денормализация

OLAP (DWH, star schema) — denormalize ради скорости queries. OLTP — normalize (3NF).

29. ACID

Atomicity, Consistency, Isolation, Durability — свойства транзакций.

30. Как посчитать retention D7

WITH cohort AS (
    SELECT user_id, MIN(DATE(event_at)) AS d0 FROM events GROUP BY user_id
)
SELECT
    COUNT(DISTINCT CASE WHEN DATE(e.event_at) = c.d0 + 7 THEN e.user_id END) /
    COUNT(DISTINCT c.user_id) AS d7_retention
FROM cohort c LEFT JOIN events e USING (user_id);

Кейсы (часто дают задачу)

Найти пользователей без покупок

SELECT u.* FROM users u
LEFT JOIN orders o USING (user_id)
WHERE o.order_id IS NULL;

Conversion воронки

SELECT
    COUNT(DISTINCT CASE WHEN event = 'signup' THEN user_id END) AS s,
    COUNT(DISTINCT CASE WHEN event = 'active' THEN user_id END) AS a,
    COUNT(DISTINCT CASE WHEN event = 'paid' THEN user_id END) AS p
FROM events;

Топ-5 клиентов по выручке

SELECT user_id, SUM(total) AS rev FROM orders GROUP BY 1 ORDER BY rev DESC LIMIT 5;

На собесе

Тренируйтесь на реальных задачах. В Карьернике — 1500+ вопросов с собесов топ-компаний.

Связанные темы

FAQ

Все ли 30 спросят?

Нет, 10-15. Готовьтесь ко всем.

Junior уровень?

1-20. Senior — все.

Что критичнее: синтаксис или идея?

Идея. Синтаксис можно чуть запутать, но если поняли задачу — ок.

Онлайн-IDE на собесе?

Иногда. Чаще на whiteboard — пишите вручную.


Готовьтесь к собесу — откройте тренажёр с 1500+ вопросами для аналитиков.