PrepBro
Профессии
PrepBro
Профессия:

Подготовка

  • Вопросы364
  • Задачи16

Аналитика

  • hh статистика
  • Анализ резюме

Практика

  • Тестовое собеседование
  • Mock-собеседование
  • Менторы

Поддержка / отзывы

Telegram админа
Профессия:

Подготовка

  • Вопросы364
  • Задачи16

Аналитика

  • hh статистика
  • Анализ резюме

Практика

  • Тестовое собеседование
  • Mock-собеседование
  • Менторы

Поддержка / отзывы

Telegram админа
Все 24 профессии
Android DeveloperData AnalystSystem Analyst1С DeveloperiOS DeveloperBusiness AnalystJava DeveloperData ScientistQA EngineerQA AutomationPHP BackendC/C++ BackendDevOps EngineerIT Project ManagerFrontend DeveloperNode.js BackendUnity DeveloperC# BackendProduct AnalystFlutter DeveloperPython DeveloperIT Product ManagerGo DeveloperData Engineer

© 2026 PrepBro. Все права защищены.

Telegram-бот

Задачи по Data Engineer

SQL: Скользящее среднее дохода за 3 месяца
2.0 Middle🔥 24💬 1

Скользящее Среднее за 3 месяца: Решение с Оконными Функциями

Это классическое задание по работе с временными рядами в SQL. Оконные функции (Window Functions) — идеальный инструмент для этого.

1. Базовое Скользящее Среднее за 3 месяца

SELECT
    DATE_TRUNC('month', date)::DATE AS month,
    SUM(income) AS monthly_income,
    AVG(SUM(income)) OVER (
        ORDER BY DATE_TRUNC('month', date)
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg_3m
FROM transactions
GROUP BY DATE_TRUNC('month', date)
ORDER BY month;

Результат:

month      | monthly_income | moving_avg_3m
2024-01-01 | 10000.00       | 10000.00
2024-02-01 | 15000.00       | 12500.00
2024-03-01 | 12000.00       | 12333.33
2024-04-01 | 18000.00       | 15000.00
2024-05-01 | 14000.00       | 14666.67

2. С Обработкой Граничных Случаев (NULLs для недостаточных данных)

Читать полностью ->
Проектирование схемы данных для дашборда РТО
3.0 Senior🔥 24💬 1

Решение

Архитектурный подход

Двухслойная архитектура:

  • Слой 1 (Hive) — холодное хранилище для истории, источник истины
  • Слой 2 (ClickHouse) — горячий слой, предаггрегированные данные для мгновенных запросов

Этот подход позволяет:

  • Хранить полную историю в Hive (долгосрочное хранение)
  • Быстро отвечать на аналитические запросы в ClickHouse
  • Переиспользовать данные для разных дашбордов и отчётов

Слой 1: Hive (Raw Data)

Таблица sales_raw — логи всех продаж

CREATE TABLE IF NOT EXISTS sales_raw (
    sale_id BIGINT,
    sale_date DATE,
    sale_timestamp TIMESTAMP,
    store_id INT,
    sku_id INT,
    category_id INT,
    city_name STRING,
    quantity INT,
    unit_price DECIMAL(10,2),
    total_amount DECIMAL(15,2),
    cost_amount DECIMAL(15,2),
    margin_amount DECIMAL(15,2),
    load_date DATE
)
PARTITIONED BY (year INT, month INT)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t';
Читать полностью ->
SQL: Удаление дубликатов из таблицы
1.8 Middle🔥 23💬 1

Решение

Анализ задачи

Дубликаты в таблице: Для каждого client_id есть несколько записей с разными updated_at. Нужно оставить только самую свежую запись для каждого клиента.

В примере:

  • client_id=100: 3 записи (id=1,2,4) → оставить id=4 (самая свежая)
  • client_id=200: 1 запись (id=3) → оставить как есть

Решение 1: SELECT последних записей (просмотр)

SELECT DISTINCT ON (client_id)
    id,
    client_id,
    balance,
    updated_at
FROM ClientBalance
ORDER BY client_id, updated_at DESC;

Только для PostgreSQL. Объяснение:

  • DISTINCT ON (client_id) — оставляет одну запись на client_id
  • ORDER BY client_id, updated_at DESC — первая запись в группе (самая свежая)

Результат:

id | client_id | balance | updated_at
---|-----------|---------|---------------------
4  | 100       | 6000.00 | 2024-01-17 12:00:00
3  | 200       | 3000.00 | 2024-01-15 09:00:00

Решение 2: С использованием ROW_NUMBER (универсальное)

Читать полностью ->
PySpark: Определение пользовательских сессий
1.7 Middle🔥 21💬 1

Решение

Задача и подход

Нужно определить сессии пользователей с интервалом инактивности менее 30 минут. Для этого используем PySpark с функциями работы с окнами (Window Functions) и обнаружением разрывов в хронологической последовательности.

Основной алгоритм:

  1. Сортируем события по user_id и event_time
  2. Вычисляем разницу времени между текущим и предыдущим событием
  3. Помечаем начало новой сессии, если разница > 30 минут
  4. Присваиваем session_id через cumsum переходов
  5. Агрегируем данные по сессиям

Шаг 1: Присвоение session_id

from pyspark.sql import SparkSession, Window
from pyspark.sql.functions import *
from datetime import datetime

spark = SparkSession.builder.appName("SessionAnalysis").getOrCreate()
Читать полностью ->
SQL: Пользователи с покупками выше среднего за 3 месяца
1.8 Middle🔥 20💬 1

Решение

Анализ требований

Задача: Найти пользователей, чьи покупки за 3 месяца превышают общее среднее значение.

Ключевые моменты:

  1. Период: последние 3 месяца
  2. Средняя сумма: по ВСЕм пользователям (не индивидуальная)
  3. Фильтр: sum(amount по пользователю) > avg(amount по всем)
  4. Вывод: имя, сумма покупок, среднее по всем

Решение 1: С использованием оконной функции AVG (рекомендуется)

Читать полностью ->
Диагностика проблем масштабирования Spark алгоритма
2.7 Senior🔥 20💬 1

Решение

Типовые проблемы масштабирования Spark алгоритмов

1. Дисбаланс данных (Data Skew)

Это самая частая проблема. Когда данные распределены неравномерно по партициям, некоторые worker'ы обрабатывают в 100+ раз больше данных, чем другие.

Пример:

# Плохо — ключи распределены неравномерно
df.groupBy("user_id").count().show()
# Если у одного пользователя 99% всех записей — один partition получит все данные

2. Недостаток памяти (Out of Memory)

  • Broadcast join больших таблиц (> 2 ГБ) в памяти каждого executor'а
  • Collect() на больших данных
  • Accumulator'ы, растущие без контроля
  • Shuffle'ы с большим числом ключей

3. Network Shuffle

При join'е двух больших таблиц Spark должен переместить данные по сети. Если объём shuffle'а больше доступной памяти, начинаются disk spills (запись на диск), что замедляет выполнение в 10+ раз.

4. Каскадные операции без кэширования

Читать полностью ->
Проектирование ETL-пайплайна для обработки 10 ТБ данных ежедневно
3.0 Senior🔥 17💬 1

Решение

1. Архитектура системы

Рекомендуемая пятислойная архитектура с разделением ответственности:

SOURCES → INGESTION → PROCESSING → WAREHOUSE → PRESENTATION

Ключевые компоненты:

  • Буфер (Kafka) для асинхронной инжекции
  • Обработчик (Spark/Beam) для трансформации
  • Хранилище (Snowflake/BigQuery) с горячим/холодным слоем
  • Оркестратор (Airflow) для управления DAG

2. Технологический стек

Ingestion Layer:

  • Apache Kafka для буферизации потоков и пакетов
  • Fivetran для готовых коннекторов к БД/API
  • Apache NiFi для файлов и нестандартных источников

Processing Layer:

  • Apache Spark 3.x для распределённой обработки
  • dbt для трансформации на SQL (версионирование, тестирование)
  • Google Dataflow/AWS Glue как альтернатива

Storage Layer:

  • Snowflake основное хранилище (columnar, масштабируемость compute/storage независимо)
  • Delta Lake на S3/GCS для промежуточных данных
  • Холодное хранилище в S3 Glacier (3+ года истории)
Читать полностью ->
SQL: Найти N-ую самую высокую зарплату
2.0 Middle🔥 17💬 1

Решение

Задача и контекст

Нужно найти N-ую самую высокую уникальную зарплату. Это важно различать:

  • N-ая самая высокая → уникальные значения (1, 2, 3, ...)
  • N-ый наибольший → с учётом дубликатов

В примере уникальные зарплаты: 100000 (Alice, Diana), 90000 (Charlie), 80000 (Bob), 75000 (Eve)

  • 1-ая (самая высокая): 100000
  • 2-ая: 90000
  • 3-ая: 80000
  • 4-ая: 75000

Решение 1: С использованием LIMIT + OFFSET (простое и быстрое)

SELECT DISTINCT salary
FROM Employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1;  -- N-1 для получения N-ой зарплаты

Для N = 2:

SELECT DISTINCT salary
FROM Employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1;
-- Результат: 90000

Объяснение:

  • DISTINCT — исключаем дубликаты зарплат
  • ORDER BY salary DESC — сортируем по убыванию
  • LIMIT 1 — берём одну строку
  • OFFSET N-1 — пропускаем первые N-1 строк

Преимущества:

  • Самый простой синтаксис
  • Быстро выполняется
  • Понимается всеми
Читать полностью ->
Реализация класса SparseVector для скалярного произведения разреженных векторов
3.0 Senior🔥 17💬 1

Решение

Задача и контекст

Необходимо реализовать класс для работы с разреженными векторами (sparse vectors) — векторами, содержащими много нулевых элементов. Эта задача критична в обработке данных, так как реальные данные часто имеют высокую разреженность (тексты, пользовательские рейтинги, сетевые графы). Наивное хранение всех элементов может привести к огромным потерям памяти.

Базовое решение

Начнём с простого подхода, используя встроенные структуры Python:

class SparseVector:
    def __init__(self, nums: list[int]):
        self.nums = nums
    
    def dotProduct(self, vec: "SparseVector") -> int:
        return sum(a * b for a, b in zip(self.nums, vec.nums))

# Пример использования
nums1 = [1, 2, 0, 4]
nums2 = [8, 0, 3, 5]
vec1 = SparseVector(nums1)
vec2 = SparseVector(nums2)
print(vec1.dotProduct(vec2))  # 28
Читать полностью ->
SQL: Определение непрерывных периодов активности
2.0 Middle🔥 16💬 1

Решение

Задача и подход

Нужно обнаружить непрерывные периоды активности (консецутивные дни) для каждого пользователя. Используем window functions для вычисления разницы между текущей датой и рангом, чтобы идентифицировать разрывы.

Основной алгоритм:

  1. Сортируем активности по дате
  2. Вычисляем ранг каждой даты
  3. Вычитаем ранг из даты — если значение одинаковое для соседних строк, они в одном периоде
  4. Группируем по найденному периоду

Шаг 1: Определение непрерывных периодов

Читать полностью ->
SQL: Маркировка узлов древовидной структуры
1.8 Middle🔥 14💬 1

Решение

Задача и подход

Нужно классифицировать узлы дерева по трём типам: Root (корень), Leaf (лист), Inner (внутренний). Используем стандартные SQL техники с самоприсоединением (self-join) для определения наличия дочерних узлов и анализа родительских связей.

Основной алгоритм:

  1. Root: узлы где parent_id IS NULL
  2. Leaf: узлы, которые не появляются как parent_id для других узлов
  3. Inner: все остальные (имеют и родителя, и детей)

Решение на SQL

SELECT 
    t.node_id,
    CASE
        WHEN t.parent_id IS NULL THEN 'Root'
        WHEN NOT EXISTS (
            SELECT 1 
            FROM tree children 
            WHERE children.parent_id = t.node_id
        ) THEN 'Leaf'
        ELSE 'Inner'
    END AS node_type
FROM tree t
ORDER BY t.node_id;

Альтернативный подход с LEFT JOIN

Читать полностью ->
SQL: Медианная зарплата по департаментам
1.7 Middle🔥 13💬 1

Решение

Понимание медианы

Медиана — значение, которое делит отсортированный набор пополам:

  • Нечётное количество: средний элемент (3, 5, 7, 9, 11) → медиана = 7
  • Чётное количество: среднее двух центральных элементов (3, 5, 7, 9) → медиана = (5+7)/2 = 6

В примере:

  • IT: [50000, 60000, 70000] (3 человека) → медиана = 60000 (средний элемент)
  • Sales: [80000, 90000] (2 человека) → медиана = (80000+90000)/2 = 85000

Решение 1: С использованием PERCENTILE_CONT (рекомендуется)

Это встроенная функция в большинстве БД для расчёта медианы:

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

Объяснение:

  • PERCENTILE_CONT(0.5) — 50-й процентиль (медиана)
  • WITHIN GROUP (ORDER BY salary) — сортировка значений для расчёта
  • GROUP BY department — вычисляем медиану в каждой группе
Читать полностью ->
Подсчёт уникальных значений в Spark с объяснением выполнения
3.0 Senior🔥 12💬 1

Решение

SQL-запрос для подсчёта уникальных значений

SELECT COUNT(DISTINCT user_id) AS unique_users
FROM large_dataset;

Альтернативная форма:

SELECT COUNT(*)
FROM (
    SELECT DISTINCT user_id
    FROM large_dataset
) t;

Детальное объяснение выполнения в Spark

Этап 1: Logical Plan (что нужно сделать)

SparkSQL создаёт логический план:

Aggregate [COUNT(DISTINCT user_id)]
└── Scan parquet large_dataset

Этап 2: Physical Plan (как это делать)

HashAggregate (final)
├── Exchange hashpartitioning(user_id, 200)  ← Shuffle происходит здесь
└── HashAggregate (partial)
    └── Scan parquet large_dataset

Что происходит:

Читать полностью ->
Проблема партиционирования по полю типа double в Spark
3.0 Senior🔥 12💬 1

Решение

Проблемы в коде с партиционированием по double

Этот код содержит критическую ошибку, которая может полностью вывести кластер из строя:

1. Дублирование каждого уникального значения

Партиционирование по полю amount типа double создаёт отдельную папку для каждого уникального значения. С диапазоном от 0 до 1 млрд значений получится миллиарды папок в HDFS, каждая под отдельное значение.

Если в датасете миллиард уникальных значений — получим миллиард папок!

2. Перегрузка NameNode

NameNode хранит в памяти весь namespace HDFS — информацию о каждом файле, папке, блоке данных. На каждую папку требуется примерно 150-250 байт памяти. Для 1 млрд папок это:

1,000,000,000 × 200 байт = 200 ГБ памяти

Типичный NameNode имеет 16-64 ГБ памяти — этого недостаточно. NameNode начнёт работать с диском, что приведёт к полной неработоспособности кластера, GC pauses на десятки секунд, невозможности выполнять какие-либо операции.

Роль NameNode

Читать полностью ->
SQL: Изменение в капитале при переводах между аккаунтами
1.7 Middle🔥 11💬 1

Решение

Задача и подход

Нужно анализировать переводы между аккаунтами: рассчитать изменение баланса, определить положительные/отрицательные балансы и найти наиболее активные пары аккаунтов.

Шаг 1: Изменение баланса для каждого аккаунта

WITH account_cash_flow AS (
    SELECT 
        account_id,
        SUM(CASE WHEN flow_type = 'sent' THEN amount ELSE 0 END) AS total_sent,
        SUM(CASE WHEN flow_type = 'received' THEN amount ELSE 0 END) AS total_received
    FROM (
        SELECT sender_id AS account_id, amount, 'sent' AS flow_type 
        FROM transactions
        UNION ALL
        SELECT receiver_id, amount, 'received' 
        FROM transactions
    ) flows
    GROUP BY account_id
)
SELECT 
    account_id,
    total_sent,
    total_received,
    total_sent - total_received AS balance_change
FROM account_cash_flow
ORDER BY balance_change DESC;

Результат: 100: -350, 200: +200, 300: +150

Шаг 2: Классификация на положительные и отрицательные

Читать полностью ->
SQL: Поиск симметричных пар в таблице
1.3 Junior🔥 6💬 1

Решение

Анализ задачи

Нужно найти пары (X₁, Y₁) и (X₂, Y₂) из одной таблицы, где:

  • X₁ = Y₂ (первый элемент первой пары = второй элемент второй пары)
  • Y₁ = X₂ (второй элемент первой пары = первый элемент второй пары)

В примере:

  • (1, 2) и (2, 1) — симметричные пары ✓
  • (5, 6) и (6, 5) — симметричные пары ✓
  • (3, 4) — нет парной строки (4, 3), поэтому не входит в результат

Решение 1: Базовое (использование JOIN)

SELECT DISTINCT
    p1.X,
    p1.Y
FROM pairs p1
JOIN pairs p2 ON p1.X = p2.Y AND p1.Y = p2.X
WHERE p1.X <= p1.Y  -- Исключаем дубликаты (выводим только меньшее значение первым)
ORDER BY p1.X ASC;

Объяснение:

  1. JOIN: Соединяем таблицу с самой собой (p1 JOIN p2)

    • p1.X = p2.Y AND p1.Y = p2.X — условие симметричности
    • Для пары (1, 2) в p1 найдётся пара (2, 1) в p2
  2. WHERE p1.X <= p1.Y: Фильтруем, чтобы вывести только одну из двух симметричных пар

    • (1, 2): 1 <= 2 ✓ → выводим
    • (2, 1): 2 <= 1 ✗ → не выводим
Читать полностью ->