Урок курса

ETL и ELT: разница на примере Python и SQL

Data Engineering Fast‑Track: Kafka → Airflow → Spark

ETL и ELT: короткий ответ

ETL означает Extract → Transform → Load: данные извлекают, преобразуют и загружают в целевую систему. ELT означает Extract → Load → Transform: сначала сохраняют исходные данные в целевой системе, затем преобразуют их там. Практическое различие — место преобразования и то, какие данные остаются доступными для повторной обработки.

Оба подхода могут приводить к одной аналитической таблице. Выбор не определяется только языком: Python не делает любой пайплайн ETL, а SQL не делает любой пайплайн ELT. Важно посмотреть, когда и где относительно загрузки выполняется Transform.

Задача: посчитать оплаченную выручку

Выгрузка заказов содержит идентификатор, статус и сумму в копейках. В ней есть оплаченные и отменённые заказы. Аналитику нужна сумма только оплаченных:

orders = [
    {"order_id": 10, "status": "paid", "amount_cents": 1000},
    {"order_id": 11, "status": "cancelled", "amount_cents": 500},
    {"order_id": 12, "status": "paid", "amount_cents": 2500},
]

Ожидаемая выручка — 3500 копеек, или 35 рублей. Дальше реализуем два пути к этому результату. Пример использует стандартную библиотеку Python и SQLite в памяти: устанавливать сервер базы данных не нужно.

ETL: преобразование перед загрузкой

В Python отфильтруем исходные записи и загрузим в целевую таблицу уже подготовленные данные:

import sqlite3

orders = [
    {"order_id": 10, "status": "paid", "amount_cents": 1000},
    {"order_id": 11, "status": "cancelled", "amount_cents": 500},
    {"order_id": 12, "status": "paid", "amount_cents": 2500},
]

prepared = [
    (row["order_id"], row["amount_cents"])
    for row in orders if row["status"] == "paid"
]

with sqlite3.connect(":memory:") as db:
    db.execute("CREATE TABLE paid_orders (order_id INTEGER PRIMARY KEY, amount_cents INTEGER)")
    db.executemany("INSERT INTO paid_orders VALUES (?, ?)", prepared)
    result = db.execute("SELECT SUM(amount_cents) FROM paid_orders").fetchone()[0]
    print(result)  # 3500

Отмена заказа в этой целевой таблице отсутствует: отбор выполнен до загрузки. Если завтра нужна статистика отмен, по одной paid_orders её не восстановить. Для этого потребуется исходная выгрузка или отдельный сырой слой. ETL не запрещает сохранять исходники отдельно, но такое хранение нужно спроектировать явно.

ELT: преобразование после загрузки

Теперь загрузим все исходные заказы и применим правило в целевой базе:

import sqlite3

orders = [(10, "paid", 1000), (11, "cancelled", 500), (12, "paid", 2500)]

with sqlite3.connect(":memory:") as db:
    db.execute("CREATE TABLE raw_orders (order_id INTEGER PRIMARY KEY, status TEXT, amount_cents INTEGER)")
    db.executemany("INSERT INTO raw_orders VALUES (?, ?, ?)", orders)
    db.execute("""
        CREATE TABLE paid_orders AS
        SELECT order_id, amount_cents
        FROM raw_orders
        WHERE status = 'paid'
    """)
    result = db.execute("SELECT SUM(amount_cents) FROM paid_orders").fetchone()[0]
    print(result)  # 3500

Фильтрация выполнена SQL-запросом внутри целевой базы после загрузки raw_orders. Исходные статусы сохранились: можно построить другую витрину без повторного запроса к источнику. Но сырой слой занимает место и тоже требует правил доступа и срока хранения. «Загрузим всё» не означает, что любой сотрудник должен читать все исходные поля.

Когда какой вариант удобнее

Сначала проверьте ограничения проекта. Может ли целевая система выполнять нужные преобразования? Требуется ли очистить или исключить часть данных до попадания в неё? Нужно ли пересчитывать историю при изменении бизнес-правила? Кто поддерживает Python-код и SQL-модели?

Для небольшого ежедневного CSV оба варианта могут быть простыми. Для большой аналитической базы преобразования часто удобно выполнять её вычислительными средствами. Если загрузчик обязан убрать ненужные поля заранее, это правило не отменяется только из-за выбора ELT.

Нередко получается гибрид: лёгкая нормализация перед загрузкой, сырой слой в хранилище и аналитические преобразования после. Название полезно для обсуждения архитектуры, но не заменяет описание конкретных стадий.

Почему повторный запуск может удвоить выручку

В учебном примере база создаётся заново. В рабочей базе повторная загрузка той же выгрузки может добавить заказы повторно. Поэтому отдельно задают ключ записи и способ обновления: например, уникальный order_id, upsert либо замену согласованной партиции.

Нужно также решить, что делать с изменившимся статусом. Заказ мог быть оплачен вчера и отменён сегодня. Если загрузчик умеет только добавлять строки, сумма останется неверной. Здесь необходима политика обновления, а не новый маркетинговый ярлык для пайплайна.

Контроль качества проверяет конкретные условия: идентификатор не пуст, сумма не отрицательна, статус известен, число строк и итоговая сумма объяснимы. Для данного примера итог 3500 — маленькая контрольная сумма, которую можно проверить вручную до запуска сложной инфраструктуры.

Где здесь Kafka, Airflow и Spark

Источник может присылать события через Kafka, оркестратор может запускать стадии и повторять их после сбоя, а обработчик — выполнять преобразования над большим объёмом данных. Наличие этих инструментов само по себе не отвечает на вопросы о дублях, поздних изменениях и бизнес-правиле.

Этот урок открыт бесплатно. В платном курсе Data Engineering можно посмотреть программу дальнейшей работы с Kafka, Airflow и Spark. Если пока не хватает SQL, начните с бесплатного курса SQL.

Справка о различии ETL/ELT: Azure Architecture Center.

Задача 1

Заказы сначала целиком загружаются в raw_orders в аналитической базе. Затем SQL-запрос в этой же базе отбирает оплаченные заказы в витрину. Какой подход описан?

Выберите один вариант
Без регистрации · результат не сохраняется

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку