dbt (data build tool) — инструмент, который превратил SQL из языка запросов в язык инженерии данных. С dbt аналитики пишут SELECT-запросы, а инфраструктура сама строит пайплайны.
Что такое dbt
dbt берёт ваши SQL SELECT-запросы и запускает их в нужном порядке, создавая таблицы или views в базе данных. Никакого Python, никакого ETL-кода — только SQL.
-- Обычный SQL-запрос (dbt-модель):
-- models/marts/user_stats.sql
SELECT
u.id,
u.email,
COUNT(o.id) AS total_orders,
SUM(o.amount) AS total_spent,
MAX(o.created_at) AS last_order_at
FROM {{ ref('stg_users') }} u
LEFT JOIN {{ ref('stg_orders') }} o ON o.user_id = u.id
GROUP BY u.id, u.email
{{ ref('stg_users') }} — ссылка на другую dbt-модель. dbt строит граф зависимостей и запускает модели в правильном порядке.
Слои трансформаций
Стандартная архитектура dbt:
Raw (исходные данные)
↓
Staging (stg_*) — очистка и переименование
↓
Intermediate (int_*) — бизнес-логика
↓
Marts (fct_*, dim_*) — финальные таблицы для аналитики
-- models/staging/stg_orders.sql
SELECT
id AS order_id,
user_id,
amount,
LOWER(status) AS status,
created_at::timestamptz AS created_at
FROM {{ source('raw', 'orders') }}
WHERE amount > 0 -- базовая очистка
-- models/marts/fct_orders.sql
SELECT
o.order_id,
o.user_id,
u.email,
u.segment,
o.amount,
o.status,
o.created_at,
DATE_TRUNC('month', o.created_at) AS order_month
FROM {{ ref('stg_orders') }} o
JOIN {{ ref('stg_users') }} u ON u.user_id = o.user_id
Материализация моделей
# dbt_project.yml
models:
my_project:
staging:
+materialized: view # лёгкие staging — вью
marts:
+materialized: table # финальные таблицы
intermediate:
+materialized: ephemeral # CTE, не создаёт объект в БД
В самой модели:
-- models/marts/daily_metrics.sql
{{ config(materialized='table', post_hook='ANALYZE {{ this }}') }}
SELECT
DATE_TRUNC('day', created_at) AS day,
COUNT(*) AS orders,
SUM(amount) AS revenue
FROM {{ ref('stg_orders') }}
GROUP BY 1
Тесты качества данных
dbt имеет встроенные тесты:
# models/staging/schema.yml
models:
- name: stg_orders
columns:
- name: order_id
tests:
- unique # нет дубликатов
- not_null # нет NULL
- name: status
tests:
- accepted_values:
values: ['pending', 'completed', 'refunded', 'cancelled']
- name: user_id
tests:
- not_null
- relationships:
to: ref('stg_users')
field: user_id
dbt test # запустить все тесты
dbt test --select stg_orders # только для одной модели
Пользовательские тесты
-- tests/test_no_future_orders.sql
-- Тест проходит если запрос возвращает 0 строк
SELECT * FROM {{ ref('stg_orders') }}
WHERE created_at > NOW()
Incremental-модели
-- models/marts/events_incremental.sql
{{ config(materialized='incremental', unique_key='event_id') }}
SELECT
id AS event_id,
user_id,
event_type,
created_at
FROM {{ source('raw', 'events') }}
{% if is_incremental() %}
-- При инкрементальном запуске — только новые строки
WHERE created_at > (SELECT MAX(created_at) FROM {{ this }})
{% endif %}
Первый запуск — полная загрузка. Последующие — только новые данные.
Seeds: загрузка CSV
# seeds/country_codes.csv
code,country_name,region
RU,Россия,Europe
US,United States,Americas
DE,Germany,Europe
dbt seed # загружает CSV как таблицу в БД
-- Использование в модели
SELECT o.*, c.country_name
FROM {{ ref('fct_orders') }} o
JOIN {{ ref('country_codes') }} c ON c.code = o.country_code
Macros: переиспользуемый SQL
-- macros/safe_divide.sql
{% macro safe_divide(numerator, denominator) %}
CASE WHEN {{ denominator }} = 0 THEN NULL
ELSE {{ numerator }}::numeric / {{ denominator }}
END
{% endmacro %}
-- Использование в модели:
SELECT
category,
conversions,
{{ safe_divide('conversions', 'sessions') }} AS cvr
FROM {{ ref('fct_sessions') }}
Документация
# models/marts/schema.yml
models:
- name: fct_orders
description: "Факт-таблица заказов. Обновляется ежедневно в 3:00 UTC."
columns:
- name: order_id
description: "Уникальный ID заказа из системы оплаты"
- name: amount
description: "Сумма заказа в рублях без НДС"
dbt docs generate # сгенерировать документацию
dbt docs serve # открыть в браузере с lineage graph
Типичный стек с dbt
PostgreSQL / Snowflake / BigQuery — хранение данных
↑ сырые данные
Airbyte / Fivetran / Stitch — репликация из источников
↓ трансформации
dbt — трансформации SQL
↓ готовые таблицы
Metabase / Superset / Tableau — дашборды
Когда dbt полезен
✅ Много SQL-трансформаций, которые зависят друг от друга
✅ Команда аналитиков пишет SQL (не Python)
✅ Нужно версионирование, тесты и документация SQL-кода
✅ Хранилище данных: Snowflake, BigQuery, Redshift, PostgreSQL
❌ Простые запросы без зависимостей
❌ Источники данных не в SQL-совместимой БД
Итог
dbt — это Git + тесты + документация + зависимости для SQL. Если вы пишете сложные SQL-трансформации и хотите порядка в коде, dbt — стандарт индустрии в 2026.
# Запуск
dbt run # выполнить все модели
dbt test # запустить все тесты
dbt build # run + test
dbt run --select my_model+ # модель и все её зависимые