Перейти к содержанию

Модели и материализации

Модель в dbt — это SQL-файл с одним SELECT. dbt превращает этот SELECT в объект в базе данных: представление, таблицу или инкрементальную таблицу. Способ превращения называется материализацией.


Четыре типа материализаций

view (по умолчанию)

dbt создаёт CREATE VIEW. Данные не хранятся — каждый запрос выполняет SELECT заново.

SQL
-- models/staging/stg_users.sql
-- Материализация: view (по умолчанию)
SELECT
  id        AS user_id,
  email,
  created_at
FROM {{ source('raw', 'users') }}
WHERE is_deleted = false

Когда использовать:

  • Лёгкие трансформации (переименование, фильтрация)
  • Staging-слой — данные меняются часто, объём небольшой
  • Не нужна предварительная агрегация

table

dbt создаёт CREATE TABLE AS SELECT. Данные материализуются физически — запрос выполняется один раз при сборке.

SQL
-- models/marts/dim_products.sql
{{ config(materialized='table') }}

SELECT
  p.product_id,
  p.name,
  p.sku,
  c.category_name,
  p.price,
  p.created_at
FROM {{ ref('stg_products') }} p
LEFT JOIN {{ ref('stg_categories') }} c
  ON p.category_id = c.category_id

Когда использовать:

  • Витрины (marts), к которым обращаются аналитики
  • Тяжёлые агрегации и JOIN
  • Данные, которые полностью пересоздаются при каждом запуске

table пересоздаёт данные целиком

При каждом dbt run таблица удаляется и создаётся заново. Для больших таблиц это может быть медленно — используй incremental.

incremental

dbt добавляет только новые или изменённые строки. При первом запуске создаёт таблицу целиком, при последующих — вставляет дельту.

SQL
-- models/marts/fct_events.sql
{{ config(
    materialized='incremental',
    unique_key='event_id'
) }}

SELECT
  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 %}

Ключевые элементы:

Элемент Назначение
materialized='incremental' Включает инкрементальный режим
unique_key='event_id' Ключ для обновления существующих строк (UPSERT)
is_incremental() Возвращает true при повторных запусках
{{ this }} Ссылка на саму таблицу (для фильтрации по последней дате)

Когда пересоздать с нуля

Если изменилась логика модели, запусти dbt run --full-refresh — dbt пересоздаст таблицу целиком, игнорируя инкрементальную логику.

ephemeral

dbt не создаёт ни таблицы, ни представления. Модель превращается в CTE, который подставляется в зависимые модели.

SQL
-- models/intermediate/int_order_amounts.sql
{{ config(materialized='ephemeral') }}

SELECT
  order_id,
  SUM(qty * unit_price) AS order_total
FROM {{ ref('stg_order_items') }}
GROUP BY order_id

При использовании {{ ref('int_order_amounts') }} в другой модели dbt подставит этот SELECT как WITH-блок:

SQL
-- Результат компиляции зависимой модели
WITH int_order_amounts AS (
  SELECT order_id, SUM(qty * unit_price) AS order_total
  FROM stg_order_items
  GROUP BY order_id
)
SELECT ...
FROM int_order_amounts

Когда использовать:

  • Промежуточные расчёты, которые не нужны аналитикам напрямую
  • Хочется уменьшить количество объектов в базе
  • Логика используется в нескольких моделях

Сравнение материализаций

Материализация Объект в БД Данные хранятся Когда подходит
view VIEW Нет Staging, лёгкие трансформации
table TABLE Да, пересоздаётся Витрины, тяжёлые JOIN
incremental TABLE Да, добавляется дельта Большие факт-таблицы, события
ephemeral — (CTE) Нет Промежуточные расчёты

Конфигурация моделей

В самом SQL-файле

Блок {{ config() }} в начале файла:

SQL
{{ config(
    materialized='table',
    schema='analytics',
    tags=['daily']
) }}

SELECT ...

В dbt_project.yml

Настройки для всех моделей в папке:

YAML
# dbt_project.yml
models:
  my_project:
    staging:
      +materialized: view       # все модели в staging/ — view
      +schema: staging
    marts:
      +materialized: table      # все модели в marts/ — table
      +schema: analytics

Приоритет конфигурации

Конфигурация в SQL-файле (config()) имеет приоритет над dbt_project.yml. Это позволяет задать общие настройки для папки и переопределить их для конкретной модели.

В schema.yml

Можно настроить модели через YAML-описания:

YAML
# models/marts/schema.yml
models:
  - name: fct_revenue
    config:
      materialized: table
      tags: ['finance', 'daily']
    description: "Таблица выручки по заказам"

Jinja в моделях

dbt использует шаблонизатор Jinja2. Это позволяет писать динамический SQL.

Переменные и условия

SQL
SELECT
  order_id,
  status,
  created_at
FROM {{ ref('stg_orders') }}
{% if target.name == 'dev' %}
  WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
{% endif %}

target.name содержит имя среды из profiles.yml. В dev-среде модель обрабатывает только последние 30 дней — быстрее для разработки.

Переменные проекта (var)

SQL
-- Использование переменной
SELECT *
FROM {{ ref('stg_orders') }}
WHERE created_at >= '{{ var("start_date", "2024-01-01") }}'

Передача при запуске:

Bash
dbt run --vars '{"start_date": "2025-01-01"}'

Макросы

Макросы — переиспользуемые SQL-шаблоны:

SQL
-- macros/cents_to_dollars.sql
{% macro cents_to_dollars(column_name) %}
  ROUND({{ column_name }}::numeric / 100, 2)
{% endmacro %}

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

SQL
SELECT
  order_id,
  {{ cents_to_dollars('amount_cents') }} AS amount_dollars
FROM {{ ref('stg_payments') }}

Структура проекта: staging → intermediate → marts

staging

Первичная очистка сырых данных. Одна модель на один источник.

  • Переименование колонок
  • Приведение типов
  • Фильтрация технических строк
  • Материализация: view
SQL
-- models/staging/stg_orders.sql
SELECT
  id         AS order_id,
  user_id,
  status,
  total_cents,
  created_at::timestamp AS created_at
FROM {{ source('raw', 'orders') }}
WHERE status != 'test'

intermediate

Промежуточные расчёты, которые используются в нескольких витринах.

  • Агрегации на уровне записи
  • JOIN нескольких staging-моделей
  • Материализация: ephemeral или view
SQL
-- models/intermediate/int_order_payments.sql
{{ config(materialized='ephemeral') }}

SELECT
  o.order_id,
  o.user_id,
  o.status,
  p.payment_method,
  p.amount
FROM {{ ref('stg_orders') }} o
LEFT JOIN {{ ref('stg_payments') }} p
  ON o.order_id = p.order_id

marts

Финальные витрины для аналитиков и дашбордов.

  • Бизнес-логика и метрики
  • Материализация: table или incremental
  • Именование: fct_ (факты), dim_ (измерения)
SQL
-- models/marts/fct_revenue.sql
{{ config(materialized='table') }}

SELECT
  o.order_id,
  o.user_id,
  d.full_name,
  o.payment_method,
  o.amount AS revenue,
  o.order_date
FROM {{ ref('int_order_payments') }} o
LEFT JOIN {{ ref('dim_users') }} d
  ON o.user_id = d.user_id
WHERE o.status = 'completed'

Полезные команды для работы с моделями

Bash
# Запустить одну модель
dbt run --select fct_revenue

# Запустить модель и все её зависимости (upstream)
dbt run --select +fct_revenue

# Запустить модель и всё, что от неё зависит (downstream)
dbt run --select fct_revenue+

# Запустить все модели в папке
dbt run --select staging.*

# Посмотреть скомпилированный SQL (без выполнения)
dbt compile --select fct_revenue

# Пересоздать incremental-модель с нуля
dbt run --select fct_events --full-refresh

Что запомнить

  • Четыре материализации: view, table, incremental, ephemeral
  • view — по умолчанию, для staging-слоя
  • incremental — для больших таблиц с дельтой, используй is_incremental() и unique_key
  • Конфигурация: config() в файле > dbt_project.yml > schema.yml
  • Jinja позволяет писать условия, использовать переменные и макросы
  • Структура: staging (очистка) → intermediate (расчёты) → marts (витрины)

Проверь себя


Что дальше?

Модели готовы — теперь нужно убедиться, что данные корректны. Разберись с тестами и документацией — как dbt проверяет качество данных и генерирует документацию.


Источники