Best practices и реальный пример витрин

Соберём всё вместе: правила хорошего проекта и сквозной пример от сырья до витрины.

Идемпотентность — свойство, при котором повторный запуск пайплайна даёт тот же результат; вместе с DRY, слоями и неймингом это фундамент здорового dbt-проекта.

Лучшие практики

ПрактикаПочему важно
Нейминг по слоям (stg_, int_, fct_, dim_)Из имени ясно, что за модель и где она в графе
Слои staging→intermediate→martsПереиспользование чистки, порядок, читаемость
ИдемпотентностьПовторный запуск безопасен, нет накопления мусора
DRY через макросы и stagingЛогика в одном месте — правишь однажды
Тесты на ключи (unique+not_null)Ловит самые опасные баги данных рано
Описания в YAMLДокументация не отстаёт от кода

Типичные ошибки (антипаттерны)

  • Нет тестов. Модели строятся, но никто не знает, корректны ли данные.
  • Монолитные модели. Одна гигантская модель на 500 строк SQL с десятком джойнов — не тестируется, не переиспользуется. Дробите на слои.
  • Неверная материализация. view для тяжёлой витрины (медленный BI) или table для всего (лишнее место); инкрементал без is_incremental().
  • Хардкод имён таблиц вместо ref()/source(). Ломает DAG и переносимость.
  • Бизнес-логика, размазанная по marts. Сложное выносят в intermediate.

dbt против обычных SQL-скриптов и против Airflow

Голые SQL-скриптыdbtAirflow
Порядок запускаВручнуюИз DAG автоматическиЗадаёт оркестрацию шагов
Тесты данныхНетВстроеныНе его задача
Документация/lineageНетАвтоНет
Зона ответственностиРазрозненные запросыТрансформация (T)Расписание и порядок шагов

dbt не конкурирует с Airflow — он живёт внутри шага оркестрации, заменяя хаос из скриптов на управляемый граф трансформаций.

Сквозной пример: витрина выручки по странам

Пройдём путь от сырья до витрины. Слой staging чистит источники:

-- models/staging/stg_orders.sql
select
    id as order_id,
    customer_id,
    cast(amount_cents as numeric) / 100.0 as amount,
    date(created_at) as order_date
from {{ source('raw', 'orders') }}

-- models/staging/stg_customers.sql
select id as customer_id, country_code
from {{ source('raw', 'customers') }}

Слой intermediate соединяет и подтягивает справочник стран (seed):

-- models/intermediate/int_orders_enriched.sql
select
    o.order_id, o.amount, o.order_date,
    cc.country_name
from {{ ref('stg_orders') }} o
join {{ ref('stg_customers') }} c on o.customer_id = c.customer_id
join {{ ref('country_codes') }} cc on c.country_code = cc.code

Слой marts — итоговая витрина (таблица):

-- models/marts/fct_revenue_by_country.sql
{{ config(materialized='table') }}
select
    order_date,
    country_name,
    sum(amount) as revenue,
    count(*)    as orders_count
from {{ ref('int_orders_enriched') }}
group by order_date, country_name

Проверим финальную агрегацию на песочнице

CREATE TABLE int_orders (order_date TEXT, country TEXT, amount NUMERIC);
INSERT INTO int_orders VALUES
  ('2026-06-01','Россия',100),('2026-06-01','Россия',150),
  ('2026-06-01','Казахстан',80),('2026-06-02','Россия',200);

select order_date, country,
       sum(amount) as revenue, count(*) as orders_count
from int_orders
group by order_date, country
order by order_date, country;

Вывод:

2026-06-01 | Казахстан | 80  | 1
2026-06-01 | Россия    | 250 | 2
2026-06-02 | Россия    | 200 | 1

Тесты и документация витрины

models:
  - name: fct_revenue_by_country
    description: "Выручка и число заказов по дате и стране."
    columns:
      - name: revenue
        description: "Суммарная выручка в рублях."
        tests: [not_null]
      - name: country_name
        tests: [not_null]
# Построить и протестировать весь пайплайн
dbt build

Итоги

  • Здоровый проект: нейминг по слоям, staging→intermediate→marts, идемпотентность, DRY, тесты на ключи, описания в YAML.
  • Антипаттерны: нет тестов, монолитные модели, неверная материализация, хардкод вместо ref().
  • dbt заменяет хаос SQL-скриптов управляемым DAG трансформаций и живёт внутри шага оркестрации (Airflow), а не вместо него.
  • Сквозной пример: source → stg → int (+seed) → fct, затем тесты и dbt build.
Проверьте себя
1. Что такое идемпотентность пайплайна dbt?
AСкорость выполнения
BПовторный запуск даёт тот же результат без накопления мусора
CШифрование данных
DКоличество моделей
2. Какой из этих пунктов — антипаттерн в dbt?
AТесты unique+not_null на ключах
BСлои staging→intermediate→marts
CМонолитная модель на 500 строк с десятком джойнов
DОписания моделей в YAML
3. Как соотносятся dbt и Airflow?
AЭто конкуренты, выбирают один
Bdbt живёт внутри шага оркестрации Airflow, отвечая за трансформацию (T), а Airflow — за расписание и порядок шагов
CAirflow — часть dbt
DЭто одно и то же