Как человек, который регулярно проводит технические собеседования в финтехе и отсматривает десятки решений на лайвкодинге, открою вам главный секрет: на технических собеседованиях почти никогда не смотрят только на голый код.
Смотрят прежде всего на то, КАК вы думаете.
И самый очевидный, сильный маркер живого аналитического мышления — это вопросы.
Почему нельзя сразу бросаться писать код
Типичная ошибка кандидатов: интервьюер открывает пустой редактор и озвучивает условие задачи (например: «Посчитай отток клиентов за прошлый месяц»). Кандидат бледнеет, замолкает и судорожно начинает строчить в редакторе `SELECT user_id, ...`.
Когда вам дают задачу, её не обязательно и даже вредно решать с первой секунды. Нормально — и даже критически важно — сначала уточнить:
- «Что именно бизнес хочет получить на выходе?»
- «Для чего этот расчет вообще используется стейкхолдерами?»
- «Точно ли мы говорим про этот показатель, а не про смежную метрику?» (например, Churn по дате последней транзакции или по явному расторжению договора?).
- «Могут ли быть NULL-значения или задвоенные события в логах?»
Для меня как нанимающего лида такой диалог — это моментальный зелёный флаг. Это сигнал, что передо мной осознанный специалист, который в реальной работе не зальет в прод неверную витрину из-за недопонимания ТЗ.
Техника «Мышление вслух»: как заставить интервьюера вам помогать
Перед тем как написать хоть одну строчку SQL, проговорите свой подход вслух:
«Смотрите: сначала мне нужно отфильтровать отмененные платежи. Затем, скорее всего, понадобится оконная функция с PARTITION BY по клиенту, чтобы найти дату предыдущего входа. А в основном запросе я сделаю разницу между датами через DATEDIFF и отберу тех, у кого интервал превысил 30 дней.»
Это не должно быть идеальным синтаксисом. Это просто ход вашей мысли.
Когда вы комментируете свои шаги по ходу решения, интервьюер понимает, куда вы движетесь. И если видит, что направление верное, но вы опечатались в синтаксисе — он сам с улыбкой подскажет: «Посмотри на условие в оконном фрейме».
А вот когда человек молчит 10 минут, пишет сложный запрос и в итоге выдает неверный результат — помогать уже невозможно. Не потому что интервьюер злой, а потому что совершенно непонятно, как кандидат думал.
Разбор боевых задач: как демонстрировать мышление на практике
Задача 1. Дедупликация записей и поиск последней транзакции
Условие: В таблице `payments` хранятся логи платежей. Из-за сетевых сбоев некоторые транзакции залогировались дважды. Нужно вывести только самый последний статус для каждого `order_id`.
Как рассуждать вслух: «Обычный GROUP BY по order_id с MAX(created_at) не вернет мне остальные атрибуты платежа без дополнительного джойна. Поэтому я использую оконную функцию ROW_NUMBER() с группировкой по order_id и сортировкой по created_at DESC. Затем заверну это в CTE и отфильтрую rn = 1.»
WITH ranked_payments AS (
SELECT
order_id,
user_id,
amount,
status,
created_at,
ROW_NUMBER() OVER (
PARTITION BY order_id
ORDER BY created_at DESC
) AS rn
FROM payments
)
SELECT
order_id,
user_id,
amount,
status,
created_at
FROM ranked_payments
WHERE rn = 1;
Задача 2. Топ-3 товара в каждой категории (RANK vs DENSE_RANK)
Условие: Найти 3 самых прибыльных товара в каждой категории каталога.
Вопрос интервьюеру: «Как мы поступаем, если у 3-го и 4-го товаров абсолютно одинаковая выручка? Должны ли мы включить оба товара (DENSE_RANK) или строго ограничить тремя строками (ROW_NUMBER)?»
WITH ranked_products AS (
SELECT
category_id,
product_id,
revenue,
DENSE_RANK() OVER (
PARTITION BY category_id
ORDER BY revenue DESC
) AS rnk
FROM product_sales
)
SELECT
category_id,
product_id,
revenue,
rnk
FROM ranked_products
WHERE rnk <= 3
ORDER BY category_id, rnk;
Задача 3. Расчет темпа роста выручки Month-over-Month (MoM)
Условие: Рассчитать помесячную выручку и процент её изменения относительно предыдущего месяца.
Как рассуждать вслух: «Сначала агрегируем выручку помесячно с помощью DATE_TRUNC. Затем используем LAG(), чтобы достать значение предыдущего месяца. При расчете процента прироста обязательно добавим проверку на деление на ноль или NULL для самого первого месяца в истории.»
WITH monthly_revenue AS (
SELECT
DATE_TRUNC('month', payment_date) AS month,
SUM(amount) AS current_revenue
FROM transactions
WHERE status = 'SUCCESS'
GROUP BY 1
),
with_lag AS (
SELECT
month,
current_revenue,
LAG(current_revenue) OVER (ORDER BY month) AS prev_revenue
FROM monthly_revenue
)
SELECT
month,
current_revenue,
prev_revenue,
ROUND(
(current_revenue - prev_revenue)::NUMERIC / NULLIF(prev_revenue, 0) * 100,
2
) AS mom_growth_pct
FROM with_lag
ORDER BY month;
Задача 4. Пользователи с непрерывной активностью 3 дня подряд (Gaps & Islands)
Условие: Найти клиентов, заходивших в банковское приложение минимум 3 дня подряд без перерывов.
Как рассуждать вслух: «Это классический паттерн Gaps & Islands ("Острова и пропуски"). Если вычесть порядковый номер дня ROW_NUMBER() из календарной даты активности, то для непрерывной серии дней эта разница останется константой. Это позволит сгруппировать дни в непрерывные интервалы.»
WITH distinct_days AS (
-- Убираем повторные заходы в течение одних суток
SELECT DISTINCT
user_id,
login_time::DATE AS login_date
FROM user_logins
),
numbered_logins AS (
SELECT
user_id,
login_date,
login_date - (ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) * INTERVAL '1 day') AS grp
FROM distinct_days
)
SELECT
user_id,
MIN(login_date) AS streak_start,
MAX(login_date) AS streak_end,
COUNT(*) AS consecutive_days
FROM numbered_logins
GROUP BY user_id, grp
HAVING COUNT(*) >= 3;
Мышление — это не скорость печати и не заучивание наизусть сотен функций. Это способность задать точный вопрос, объяснить логику и разложить сложную задачу на прозрачные шаги.
На индивидуальных занятиях в программе подготовки к собеседованиям мы отрабатываем именно этот навык: моделируем живой лайвкодинг, тренируем мышление вслух и доводим решение типовых финтех-задач до автоматизма.