dbt для аналитика: шпаргалка

Что такое dbt

dbt (Data Build Tool) — инструмент для transformation данных в DWH. SQL + версионирование + тесты + документация в одном.

Концепция: ELT вместо ETL. Сначала загружаем сырые данные в DWH, потом трансформируем через SQL в dbt.

Структура проекта

dbt_project/
├── dbt_project.yml       # конфигурация
├── models/
│   ├── staging/           # очистка сырых данных
│   │   └── stg_orders.sql
│   ├── marts/             # бизнес-модели
│   │   ├── fct_orders.sql
│   │   └── dim_users.sql
│   └── schema.yml         # тесты и документация
├── macros/                # Jinja-макросы
├── seeds/                 # CSV для загрузки
├── tests/                 # кастомные тесты
└── snapshots/             # Slowly Changing Dimensions

Простая модель

-- models/staging/stg_orders.sql

{{ config(
    materialized='view',
    schema='staging'
) }}

SELECT
    id AS order_id,
    user_id,
    amount,
    status,
    created_at
FROM {{ source('raw', 'orders') }}
WHERE status IS NOT NULL
  • {{ source('raw', 'orders') }} — ссылка на сырую таблицу.
  • {{ config(...) }} — настройки материализации.

Типы материализации

-- View: виртуальная, пересчитывается при каждом запросе
{{ config(materialized='view') }}

-- Table: физическая таблица, пересоздаётся при dbt run
{{ config(materialized='TABLE') }}

-- Incremental: добавляет только новые данные
{{ config(materialized='incremental') }}

-- Ephemeral: CTE, не материализуется
{{ config(materialized='ephemeral') }}

Incremental пример

{{ config(materialized='incremental', unique_key='order_id') }}

SELECT * FROM {{ source('raw', 'orders') }}
{% if is_incremental() %}
WHERE created_at > (SELECT MAX(created_at) FROM {{ this }})
{% endif %}

ref() — ссылки между моделями

-- models/marts/fct_daily_revenue.sql

SELECT
    DATE_TRUNC('day', created_at)::DATE AS day,
    SUM(amount) AS revenue
FROM {{ ref('stg_orders') }}
WHERE status = 'paid'
GROUP BY 1

{{ ref('stg_orders') }} — ссылка на другую модель. dbt автоматически строит DAG зависимостей.

Больше таких примеров с разборами — в Telegram-тренажёре. Короткие сессии, прогресс по темам, объяснения после каждого ответа.

Тесты

Generic тесты в schema.yml

version: 2

models:
  - name: stg_orders
    columns:
      - name: order_id
        tests:
          - unique
          - not_null
      - name: user_id
        tests:
          - not_null
          - relationships:
              to: ref('stg_users')
              field: user_id
      - name: status
        tests:
          - accepted_values:
              values: ['paid', 'pending', 'refunded']

Кастомные тесты

-- tests/assert_positive_amounts.sql
SELECT * FROM {{ ref('stg_orders') }}
WHERE amount < 0
-- Тест фейлится, если возвращаются строки

Запуск

dbt test               # все тесты
dbt test -s stg_orders  # конкретная модель

Jinja и макросы

Переменные

{% SET currencies = ['USD', 'EUR', 'RUB'] %}

SELECT
{% for c IN currencies %}
    SUM(CASE WHEN currency = '{{ c }}' THEN amount END) AS total_{{ c | lower }},
{% endfor %}
    SUM(amount) AS total
FROM orders

Макросы

-- macros/calculate_revenue.sql
{% macro calculate_revenue(TABLE, date_col) %}
SELECT
    DATE_TRUNC('month', {{ date_col }})::DATE AS month,
    SUM(amount) AS revenue
FROM {{ TABLE }}
GROUP BY 1
{% endmacro %}

Использование:

{{ calculate_revenue(ref('stg_orders'), 'created_at') }}

Документация

В schema.yml:

models:
  - name: fct_orders
    description: "Основная фактовая таблица заказов"
    columns:
      - name: order_id
        description: "Уникальный ID заказа"
      - name: amount
        description: "Сумма заказа в рублях, до вычета комиссии"

Генерируется HTML-доки:

dbt docs generate
dbt docs serve  # открывает браузер

Команды

# Запустить все модели
dbt run

# Конкретную модель + её зависимости
dbt run -s +fct_orders+

# Только staging
dbt run -s staging

# Тесты
dbt test

# Документация
dbt docs generate && dbt docs serve

# Parse только (проверка синтаксиса)
dbt parse

# Full refresh incremental
dbt run -s fct_orders --full-refresh

Snapshots — SCD Type 2

История изменений таблицы:

-- snapshots/users_snapshot.sql
{% snapshot users_snapshot %}
{{
    config(
        target_schema='snapshots',
        unique_key='user_id',
        strategy='check',
        check_cols=['email', 'status']
    )
}}
SELECT * FROM {{ source('raw', 'users') }}
{% endsnapshot %}

Каждый запуск — проверяет изменения и сохраняет историю.

Если готовишься к собесу — бот @kariernik_bot закрывает 80% технических вопросов. SQL, Python, A/B, продуктовые метрики — всё в одном месте.

Seeds

CSV-файлы, которые загружаются как таблицы:

seeds/
  country_list.csv
dbt seed

Используется для справочников: список стран, валют, категорий.

Почему dbt

Плюсы

  • Версионирование. Код в Git.
  • Тесты. Автоматическая проверка качества.
  • Документация. Автогенерация.
  • DAG зависимостей. Не пересчитываешь всё.
  • Модульность. Переиспользуемые макросы.

Минусы

  • Learning curve. Jinja, концепции.
  • Только SQL (и Python в dbt-py).
  • Требует DWH. Не для transaction DB.

Аналитик и dbt

Junior

Писать staging-модели, базовые факты/dims.

Middle

Incremental модели, macros, тестирование.

Senior / Analytics Engineer

Архитектура проекта, performance, CI/CD.

В современных компаниях dbt — стандарт для transformation. Упоминание в резюме — плюс.

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

FAQ

dbt vs SQL-views?

dbt даёт тесты, версионирование, документацию и автоматическую пересборку. Plain views — только SQL.

Нужен ли dbt для маленькой команды?

Если меньше 5 моделей — можно без него. Если 10+ — dbt спасает от хаоса.

dbt Cloud или dbt Core?

Core — бесплатный, самоhosted. Cloud — платный с UI, IDE, CI/CD.

Python в dbt?

Есть dbt-py (для Snowflake, Databricks, BigQuery). Позволяет писать ML-feature engineering прямо в dbt.