Роль dbt: staging, intermediate, marts
В прошлых уроках мы говорили о слоях (medallion) и о моделях (star schema, OBT), но обходили вопрос: чем всё это реально строят? Кто-то должен описать трансформации, которые превращают сырьё в витрины. В мире ELT стандартный ответ — dbt. Это не очередная методология моделирования, а инструмент, который превращает методологию в работающий код. Этот урок — про то, как dbt организует трансформации в слои, генерирует surrogate keys и реализует SCD2.
Коротко, что такое dbt. Это инструмент трансформаций для ELT: данные уже загружены в warehouse сырыми, dbt описывает SQL-трансформации, превращающие сырьё в модели. Каждая модель dbt — это файл с SQL-запросом SELECT; dbt сам строит из них граф зависимостей и выполняет в правильном порядке. Для нас важно одно: dbt задаёт дисциплину слоёв, и эта дисциплина — прямое продолжение всего, что вы уже изучили.
Три слоя dbt: staging, intermediate, marts
dbt-проект по соглашению организован в три слоя трансформаций. Названия отличаются от medallion (bronze/silver/gold), но идея слоёв зрелости — та же.
Staging — нижний слой. Правило строгое: один staging-файл на одну таблицу источника. Staging-модель делает только лёгкие операции — переименование столбцов в единый стиль, приведение типов, простейшую очистку. Никаких JOIN, никакой бизнес-логики. Staging — это «причёсанные кирпичики»: каждая source-таблица в чистом, предсказуемом виде.
-- models/staging/stg_orders.sql
-- один файл на одну source-таблицу: переименование и типизация
SELECT
id AS order_id,
cust_id AS customer_id,
CAST(dt AS DATE) AS order_date,
CAST(amt AS DECIMAL(12,2)) AS amount
FROM {{ source('shop', 'orders') }}
Intermediate — средний слой. Здесь staging-модели соединяются, применяется бизнес-логика, считаются промежуточные результаты. Intermediate-модели — рабочие, их не отдают конечному потребителю напрямую; они существуют, чтобы разбить сложную трансформацию на читаемые шаги.
-- models/intermediate/int_orders_joined.sql
-- комбинируем staging-модели, добавляем бизнес-логику
SELECT
o.order_id, o.order_date, o.amount,
c.customer_segment,
o.amount * 0.2 AS estimated_tax
FROM {{ ref('stg_orders') }} o
JOIN {{ ref('stg_customers') }} c ON o.customer_id = c.customer_id
Marts — верхний слой. Это финальные витрины: размерные модели — dim_customer, fct_orders — или OBT. Именно marts видят аналитики и BI. Marts собираются из intermediate и staging.
Обратите внимание на ref() в коде. Когда одна модель ссылается на другую через {{ ref('stg_orders') }}, dbt запоминает зависимость и строит граф (DAG). Из графа dbt сам выводит порядок: staging раньше intermediate, intermediate раньше marts. Вам не нужно вручную задавать очерёдность — она следует из ссылок.
| Слой dbt | Аналог в medallion | Что делает | Префикс |
|---|---|---|---|
| staging | около bronze/silver | Один файл на источник: переименование, типы | stg_ |
| intermediate | silver | Соединение, бизнес-логика, промежуточные шаги | int_ |
| marts | gold | Финальные dimensions и facts | dim_ / fct_ |
Это та же идея слоёв зрелости, что и в medallion, но выраженная как структура файлов проекта. dbt не изобретает новую теорию — он даёт дисциплину, в которой теория из предыдущих модулей становится поддерживаемым кодом.
Surrogate keys в dbt
В модуле про ключи вы узнали про surrogate keys — искусственные идентификаторы. В размерной модели каждой dimension нужен surrogate key. dbt даёт для этого готовый инструмент — макрос generate_surrogate_key из пакета dbt_utils.
Макрос берёт один или несколько столбцов, конкатенирует их и считает хеш — получается detерминированный surrogate key. По сути это ровно тот же приём, что hash key в Data Vault: хеш от бизнес-ключа.
-- surrogate key dimension = хеш от business key
SELECT
{{ dbt_utils.generate_surrogate_key(['customer_id']) }} AS customer_key,
customer_id,
customer_name,
customer_city
FROM {{ ref('stg_customers') }}
-- для составного ключа — несколько столбцов:
-- {{ dbt_utils.generate_surrogate_key(['order_id', 'line_number']) }}
Почему хеш, а не автоинкремент? Те же причины, что в уроке про hash keys: хеш детерминирован (один business key всегда даёт один surrogate key — модель можно пересобрать, ключи не «поедут») и не требует централизованного счётчика (важно для параллельной обработки). Макрос обрабатывает разделители и NULL за вас. Это прямое применение того, что вы уже знаете о hash-based ключах, — просто упакованное в одну строку.
Заметьте сквозную линию курса: hash как surrogate key встречается в Data Vault (hash key), в Data Vault для change detection (hashdiff) и здесь, в dbt (generate_surrogate_key). Это один и тот же фундаментальный приём — детерминированный идентификатор из хеша бизнес-данных. Поняв его один раз, вы узнаёте его везде.
SCD2 в dbt: snapshots
В модуле про Slowly Changing Dimensions вы разобрали SCD Type 2: при изменении атрибута dimension добавляется новая строка с новым surrogate key, effective/end date и current flag, а старая версия сохраняется. Реализовать SCD2 руками — это аккуратный MERGE с закрытием старой версии и открытием новой; ошибиться легко.
dbt даёт для SCD2 встроенный механизм — snapshots. Вы описываете, за какой таблицей следить и как определять изменения, а dbt сам ведёт SCD2-логику.
-- snapshots/customers_snapshot.sql
{% snapshot customers_snapshot %}
{{ config(
target_schema='snapshots',
unique_key='customer_id',
strategy='check',
check_cols=['customer_name', 'customer_city', 'customer_segment']
) }}
SELECT * FROM {{ source('shop', 'customers') }}
{% endsnapshot %}
Что здесь происходит. unique_key — бизнес-ключ сущности. strategy='check' со списком check_cols означает: при каждом запуске dbt сравнивает значения этих столбцов с последней сохранённой версией — если хоть один изменился, фиксируется новая версия. Узнаёте? Это та же идея, что hashdiff в Data Vault: следим за набором атрибутов и реагируем на изменение. (Альтернативная стратегия — timestamp: dbt смотрит на столбец «последнего обновления».)
При запуске dbt snapshot инструмент сам ведёт служебные столбцы SCD2:
customer_id | customer_city | dbt_valid_from | dbt_valid_to
1001 | Москва | 2026-01-10 00:00:00 | 2026-03-20 00:00:00
1001 | Казань | 2026-03-20 00:00:00 | (null)
dbt_valid_from и dbt_valid_to — это effective date и end date из урока про SCD2. У текущей версии dbt_valid_to равен NULL. Когда атрибут меняется, dbt закрывает старую строку (проставляет dbt_valid_to) и вставляет новую — ровно механика SCD2, только описанная декларативно. Вы говорите «веди SCD2 за этой таблицей», dbt делает MERGE правильно.
Важная деталь: snapshot нужно запускать регулярно. Источник хранит только текущее состояние; snapshot ловит изменения, лишь когда видит их при очередном запуске. Если между двумя запусками атрибут изменился дважды — промежуточное значение не попадёт в историю. Поэтому snapshot ставят на расписание, согласованное с тем, как часто меняются данные.
dbt как клей всего курса
Соберём картину. dbt не добавляет новой теории моделирования — он операционализирует теорию из предыдущих модулей:
- Слои (medallion) -> staging / intermediate / marts как структура проекта.
- Surrogate keys ->
generate_surrogate_key, хеш от бизнес-ключа. - SCD Type 2 -> snapshots с
dbt_valid_from/dbt_valid_to. - Граф зависимостей ->
ref()строит DAG, dbt выводит порядок выполнения.
Поэтому dbt — стандартный инструмент аналитического инженера 2025-2026: он берёт концепции, которые вы изучили, и даёт способ выразить их как версионируемый, тестируемый, поддерживаемый код. В следующем уроке поднимемся ещё выше — к semantic layer, где определяются уже не таблицы, а сами бизнес-метрики.
Попробуй сам
- Возьмите две source-таблицы (
orders,customers) и распишите для каждой staging-модель: какие столбцы переименуете, какие типы приведёте. Помните правило — один staging-файл на одну source-таблицу. - Спроектируйте одну intermediate-модель, которая соединяет эти две staging-модели и добавляет вычисляемый столбец.
- Для
dim_customerнапишите строку сgenerate_surrogate_key. Объясните, почему хеш-ключ лучше автоинкремента при пересборке модели. - Опишите snapshot для таблицы
customersсо стратегиейcheck: какиеcheck_colsвы укажете? Объясните, почему snapshot нужно запускать регулярно и что теряется при редком запуске.