SQL на собеседовании аналитика: что спрашивают

Почему SQL важен

SQL — самый частый практический вопрос на собесах аналитика. В 90% собеседований в tech на middle+ уровне есть SQL-секция.

Даже если в работе вы используете Python/pandas больше, SQL остаётся универсальным языком взаимодействия с данными. От собесного SQL зависит, будет ли offer.

Хорошая новость: репертуар задач ограничен. 20-30 паттернов покрывают 90% вопросов.

Формат SQL-секции

Варьируется:

Live coding. Решаете задачу в онлайн-редакторе (LeetCode, HackerRank, собственный) с screen share. 30-45 минут, 2-3 задачи.

Whiteboard (устно). На whiteboard / в Zoom-chat пишете решения. Интервьюер оценивает логику больше синтаксиса.

Take-home. Даётся 2-24 часа на решение сложной задачи. Реже, но бывает.

В любом формате принцип одинаков: понять задачу, думать вслух, проверить решение.

Типы задач

1. Filtering + aggregation. Самый base level.

«Найди топ 10 клиентов по sum purchases за последний год».

2. Joins. Multiple tables.

«Покажи пользователей и их последнюю покупку».

3. Window functions. Middle+.

«Для каждого пользователя второй заказ», «running total», «rank within group».

4. Date manipulation.

«DAU за последние 30 дней», «users who came back on Day 7».

5. CTE и subqueries.

Структурирование complex queries.

6. Self-joins.

«Пары consecutive purchases», «менеджеры и подчинённые».

7. Pivots.

«Распредели по columns: платформы или категории».

8. Cohort/retention.

«Retention by signup cohort by Day N».

Задача: Top N per group

Классика.

«Для каждого отдела найди 2 самых высокооплачиваемых сотрудника».

Решение через ROW_NUMBER():

WITH ranked AS (
    SELECT
        department,
        name,
        salary,
        ROW_NUMBER() OVER (
            PARTITION BY department
            ORDER BY salary DESC
        ) AS rn
    FROM employees
)
SELECT department, name, salary
FROM ranked
WHERE rn <= 2;

Вариации:

  • RANK() если ties должны получить одинаковый ранг.
  • DENSE_RANK() если после tied ranks не пропускать.

Задача: Running total

«Посчитай cumulative sum revenue по дням».

SELECT
    DATE,
    revenue,
    SUM(revenue) OVER (ORDER BY DATE) AS cumulative_revenue
FROM daily_revenue
ORDER BY DATE;

Window без PARTITION — по всем rows.

Для per-group running:

SUM(revenue) OVER (PARTITION BY customer_id ORDER BY DATE)

Задача: DAU и MAU

«Посчитай DAU на каждый день за последний месяц».

SELECT
    DATE(event_time) AS day,
    COUNT(DISTINCT user_id) AS dau
FROM events
WHERE event_time >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY DATE(event_time)
ORDER BY day;

MAU rolling: 30-дневное окно на каждый день.

WITH daily_users AS (
    SELECT DATE(event_time) AS day, user_id
    FROM events
    GROUP BY DATE(event_time), user_id
)
SELECT
    day,
    COUNT(DISTINCT user_id) OVER (
        ORDER BY day
        RANGE BETWEEN INTERVAL '29 days' PRECEDING AND CURRENT ROW
    ) AS mau_rolling
FROM daily_users;

Последнее — advanced. Некоторые СУБД не поддерживают COUNT DISTINCT в window.

Задача: Retention

«Для каждой signup cohort покажи retention на Day 1, 7, 30».

WITH cohorts AS (
    SELECT
        user_id,
        DATE(signup_time) AS signup_date
    FROM users
),
activity AS (
    SELECT DISTINCT
        user_id,
        DATE(event_time) AS active_date
    FROM events
)
SELECT
    c.signup_date AS cohort,
    COUNT(DISTINCT c.user_id) AS cohort_size,
    COUNT(DISTINCT CASE
        WHEN a.active_date = c.signup_date + INTERVAL '1 day' THEN c.user_id
    END) * 1.0 / COUNT(DISTINCT c.user_id) AS day_1_retention,
    COUNT(DISTINCT CASE
        WHEN a.active_date = c.signup_date + INTERVAL '7 days' THEN c.user_id
    END) * 1.0 / COUNT(DISTINCT c.user_id) AS day_7_retention
FROM cohorts c
LEFT JOIN activity a ON c.user_id = a.user_id
WHERE c.signup_date >= CURRENT_DATE - INTERVAL '60 days'
GROUP BY c.signup_date
ORDER BY c.signup_date;

Задача: Consecutive events

«Найди пользователей с 3 днями подряд активности».

Window trick:

WITH daily_activity AS (
    SELECT DISTINCT user_id, DATE(event_time) AS active_day
    FROM events
),
numbered AS (
    SELECT
        user_id,
        active_day,
        active_day - INTERVAL '1 day' * ROW_NUMBER() OVER (
            PARTITION BY user_id ORDER BY active_day
        ) AS streak_group
    FROM daily_activity
),
streaks AS (
    SELECT
        user_id,
        streak_group,
        COUNT(*) AS streak_length
    FROM numbered
    GROUP BY user_id, streak_group
)
SELECT DISTINCT user_id
FROM streaks
WHERE streak_length >= 3;

Trick: вычитание row_number из даты даёт одинаковый value для consecutive days.

Задача: Second purchase

«Для каждого user найди дату второго заказа».

SELECT
    user_id,
    order_date AS second_purchase
FROM (
    SELECT
        user_id,
        order_date,
        ROW_NUMBER() OVER (
            PARTITION BY user_id ORDER BY order_date
        ) AS rn
    FROM orders
) t
WHERE rn = 2;

Вариации: «разница в днях между 1 и 2 purchase» — использовать LAG():

SELECT
    user_id,
    order_date - LAG(order_date) OVER (
        PARTITION BY user_id ORDER BY order_date
    ) AS days_between
FROM orders;

Задача: Median

«Найди медианную зарплату по отделам».

PostgreSQL:

SELECT
    department,
    PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_salary
FROM employees
GROUP BY department;

Без percentile — через window functions:

SELECT DISTINCT
    department,
    FIRST_VALUE(salary) OVER (
        PARTITION BY department
        ORDER BY CASE WHEN salary_rank_asc = salary_rank_desc THEN 0
                     WHEN salary_rank_asc - salary_rank_desc = 1 THEN 0
                     ELSE 1 END,
                ABS(salary_rank_asc - salary_rank_desc)
    ) AS median
FROM (
    SELECT department, salary,
        RANK() OVER (PARTITION BY department ORDER BY salary) AS salary_rank_asc,
        RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS salary_rank_desc
    FROM employees
) t;

Complexity — тест на deep SQL knowledge. Редко прямо в собесе.

Задача: NTH value

«Найди 3-й по величине продажу в каждой категории».

SELECT category, sale_amount
FROM (
    SELECT
        category,
        sale_amount,
        DENSE_RANK() OVER (
            PARTITION BY category
            ORDER BY sale_amount DESC
        ) AS rnk
    FROM sales
) t
WHERE rnk = 3;

Задача: Gaps and islands

«Найди пропущенные даты в последовательности».

WITH bounds AS (
    SELECT
        DATE,
        LAG(DATE) OVER (ORDER BY DATE) AS prev_date
    FROM events_calendar
)
SELECT
    prev_date + INTERVAL '1 day' AS gap_start,
    DATE - INTERVAL '1 day' AS gap_end
FROM bounds
WHERE DATE - prev_date > 1;

Подготовка к SQL-интервью — главный priority для аналитика. В тренажёре Карьерник собрано 500+ SQL-задач с реальных собесов с разборами и объяснениями.

Популярные задачи по компаниям

Яндекс. Retention, cohort analysis, window functions, date arithmetic.

Тинькофф. Financial aggregations, time series, transactions analysis.

Avito. Listings, user behavior, session analysis, funnels.

Ozon. Orders, inventory, recommendations, conversion funnels.

Сбер. Payments, anti-fraud, financial risks.

Альфа-Банк. Transaction analytics, customer segmentation.

Каждая компания имеет свои любимые типы задач — учитывайте в подготовке.

Live coding tips

1. Думать вслух. «I'd start with... join... then aggregate... filter».

2. Обсудить approach перед кодом. Понять, правильное ли direction.

3. Начать с small example. «Допустим, у нас 3 user, каждый с 2 orders». Проверить query вручную.

4. Проверить edge cases. NULL, дубликаты, zero values, empty results.

5. Читать свой код. «Здесь я группирую по..., фильтрую...». Показывает, что понимаете что написали.

6. Спрашивать про formats. «Даты в string или date type?» «Duplicate rows?» Хороший sign.

Типичные ошибки на собесе

Писать сразу final query. Без обсуждения approach. Рискованно.

Syntax errors из-за panic. Печатать без внимания. Checking решение перед submit.

Не использовать CTE. Сложные nested subqueries. Сложнее читать и debug. CTE are preferred.

Забыть NULL handling. COUNT(*) vs COUNT(col) vs COUNT(DISTINCT col) — разница.

Ignore performance. Для crud собесов нет, но senior-level может спросить «какая была бы сложность?» или «как бы оптимизировать?».

Stuck in silence. Если не знаете answer, talk through thinking. Иногда interviewer даёт hints.

Как готовиться

Решать задачи. LeetCode SQL, StrataScratch, DataLemur. Начните с medium, потом hard.

Paper / whiteboard practice. Не только на компьютере. Некоторые собесы без autocomplete.

Review SQL features. Window functions, regex, JSON operators, CTE — часто missing в beginner knowledge.

Mock interviews. С друзьями или coaches. Real pressure experience.

Reading real interview questions. Glassdoor, specific blogs — хорошая source common patterns.

Читайте также

FAQ

Какой SQL dialect учить?

PostgreSQL — universal baseline. В компании могут быть MySQL, ClickHouse, BigQuery — синтаксис slightly differs.

Нужны ли SQL сертификаты?

Не решающие. Real projects, GitHub, LinkedIn примеры решённых задач — более ценно.

Запоминать все Windows functions?

Main — ROW_NUMBER, RANK, LAG, LEAD, SUM OVER. Остальные — справочно.

Если не знаю синтаксис?

Lookup — OK в take-home. В live — вспомните логически, explain что хотите сделать, interviewer подскажет синтаксис.