Обзор dbt: SQL-модели, зависимости и проверки — это разбор того, как инструмент превращает набор SQL-запросов в управляемый проект. Вы пишете select, описываете зависимости через ref, задаёте материализацию и навешиваете проверки. Дальше разберём каждый слой на учебном наборе orders.
Задача dbt и место в ELT
Документация проекта на docs.getdbt.com описывает dbt как инструмент, который преобразует сырые данные хранилища в доверенные data products. Вы пишете простые SQL select-запросы, а dbt берёт на себя создание модульных и поддерживаемых моделей для аналитики, операций и AI. Это преобразование после загрузки, то есть буква T в ELT.
Модели лежат поверх уже загруженных таблиц. Документация по моделям на docs.getdbt.com прямо говорит: при выполнении dbt run вы запускаете модель, которая преобразует данные, не покидая хранилище. Исходная таблица приходит из вашего ingestion-слоя, а dbt отвечает за слой преобразования.
Что это даёт на практике. Вместо разрозненных запросов, которые каждый аналитик хранит у себя, проект собирает логику в одном месте. Документация перечисляет выгоды: отсутствие boilerplate-кода для создания таблиц и представлений, модульность, повторное использование, быстрые сборки на инкрементальных моделях, тесты и автогенерируемая документация, а также workflow из мира разработки — версионирование, ветки, pull requests, CI/CD.
Учебный контекст. Возьмём таблицу orders с тремя строками: заказ 1 от 2026-01-01 на сумму 100 со статусом paid, заказ 2 от 2026-01-01 на сумму 40 со статусом refund, заказ 3 от 2026-01-02 на сумму 70 со статусом paid. Это учебный вход. Запуск не проводился. Модель не извлекает данные из CRM: исходная таблица уже загружена в хранилище.
Отдельно про границы. dbt seed умеет загружать небольшие CSV-файлы как справочники и фикстуры, но общим загрузчиком из CRM или API инструмент не является. Задача dbt начинается там, где данные уже лежат в хранилище.
SQL-модель и ref
Модель — это один файл с финальным select-запросом. Документация по моделям на docs.getdbt.com уточняет: модели пишутся преимущественно как select и сохраняются в .sql-файл. Проект — это директория с .yml-файлом конфигурации и файлами моделей .sql или .py. Проект может содержать несколько моделей, и модели могут ссылаться друг на друга.
Ключевой элемент — ref. Он связывает одну модель с другой и одновременно объявляет зависимость. Когда вы пишете ref(‘orders’), dbt понимает, что текущая модель зависит от orders, и строит порядок выполнения сам. Вам не нужно вручную прописывать последовательность запуска.
Учебная модель daily_paid. Задача: отфильтровать строки со статусом paid, сгруппировать по дню и посчитать сумму amount. Ожидаемый результат: 2026-01-01 → 100, 2026-01-02 → 70. Файл модели выглядит так:
-- models/daily_paid.sql
select
day,
sum(amount) as paid_amount
from {{ ref('orders') }}
where status = 'paid'
group by day
Здесь ref(‘orders’) делает две вещи: подставляет актуальное имя таблицы orders в конкретном окружении и регистрирует зависимость daily_paid от orders. Строковая подстановка имени таблицы без ref сломала бы граф зависимостей.
Почему это важно для порядка сборки. dbt строит DAG — направленный граф зависимостей — и выполняет модели в порядке, где upstream-модели идут раньше downstream. При dbt build выбранные ресурсы выполняются в DAG-порядке с учётом их зависимостей. Документация по команде build на docs.getdbt.com описывает это как выполнение в DAG-порядке для выбранных ресурсов или всего проекта.
Учебный набор и ожидаемые строки составлены вручную. Успешная сборка модели подтверждает, что SQL выполнился и строки появились. Бизнес-определение выручки она не подтверждает: это отдельный вопрос к постановке задачи.
Материализация
Материализация определяет, каким объектом модель станет в хранилище. Документация проекта на docs.getdbt.com перечисляет, что dbt берёт на себя материализацию, транзакции, DDL и изменения схемы. Вы пишете бизнес-логику в select, а физическое представление результата настраивается отдельно.
Основные варианты, которые упоминает документация: view и table как базовые формы, инкрементальные модели для больших наборов с отдельно настроенной обработкой изменений. Инкрементальные модели документация называет способом ускорить длительные сборки за счёт метаданных.
Как выбрать форму. Ниже сравнение по явным критериям.
| Критерий | view | table | incremental |
|---|---|---|---|
| Что создаётся | представление поверх запроса | физическая таблица | таблица с настроенной обработкой изменений |
| Когда пересчитывается | при каждом обращении | при каждом запуске | при запуске, по заданным условиям |
| Объём данных | небольшие и средние | средние и крупные | крупные, растущие |
| Скорость чтения | зависит от запроса | быстрая | быстрая |
| Сложность настройки | минимальная | минимальная | выше, нужны условия отбора |
Практическое правило для учебного примера. daily_paid с двумя строками результата логично оставить view: пересчёт дешёвый, данные всегда свежие. Для таблицы фактов на миллионы строк имеет смысл table или incremental.
Настройка задаётся в конфигурации модели. Учебный пример с явной материализацией:
-- models/daily_paid.sql
{{ config(materialized='view') }}
select
day,
sum(amount) as paid_amount
from {{ ref('orders') }}
where status = 'paid'
group by day
Материализация влияет на стоимость. view не хранит данные и платит за пересчёт при чтении. table хранит результат и платит за перезапись при каждом запуске. Для incremental объём обработки зависит от настройки модели и выбранного запуска. Выбор зависит от того, как часто модель читают и как быстро растёт источник.
Тест данных и unit test
Тесты данных — это утверждения о моделях и других ресурсах проекта. Документация по data tests на docs.getdbt.com описывает механику: тест — это SQL-запрос, который ищет строки, опровергающие утверждение. Если тест на уникальность колонки, запрос выбирает дубликаты. Если тест на отсутствие null, запрос ищет null. Ноль возвращённых строк означает PASS.
Из коробки документация называет четыре generic-теста: unique, not_null, accepted_values и relationships. Первые два не требуют дополнительных входов, вторые два принимают аргументы. В v2 входы теста обязаны лежать под ключом arguments, иначе валидация вернёт ошибку.
Учебные проверки для нашего набора:
| Тест | Ресурс | Колонка | Что проверяет |
|---|---|---|---|
| unique | orders | order_id | нет дубликатов идентификатора |
| not_null | orders | order_id | идентификатор заполнен |
| accepted_values | orders | status | значения из {paid, refund} |
| unique | daily_paid | day | один день — одна строка |
| not_null | daily_paid | day | день заполнен |
YAML-описание проверок:
# models/schema.yml
models:
- name: orders
columns:
- name: order_id
data_tests:
- unique
- not_null
- name: status
data_tests:
- accepted_values:
arguments:
values: ['paid', 'refund']
- name: daily_paid
columns:
- name: day
data_tests:
- unique
- not_null
Контрольный дефект: добавьте в модель orders дубликат order_id=1 и повторите dbt build. Тест unique на orders вернёт нарушение; при стандартной ошибке downstream-модель daily_paid будет пропущена. Отдельно выполненный SELECT daily_paid способен агрегировать дубликаты, поэтому успешное выполнение SQL не подтверждает уникальность order_id.
Unit test — отдельный механизм. Документация по data tests уточняет, что ключ tests: переименован в data_tests: после появления unit-тестов, при этом tests: поддерживается как алиас. YAML-описания unit-тестов должны лежать в model-paths, например в models/. Unit-тесты проверяют логику модели на заданных входных строках до материализации.
Порядок в dbt build. Документация по команде build на docs.getdbt.com описывает последовательность: unit-тесты выполняются на SQL-модели, затем модель материализуется, затем на ней выполняются data tests. Такой порядок экономит расход хранилища: модель материализуется только при успешных unit-тестах.
Учебный контракт
Соберём полный учебный контракт. Запуск не проводился.
Входные данные orders:
order_id | day | amount | status
1 | 2026-01-01 | 100 | paid
2 | 2026-01-01 | 40 | refund
3 | 2026-01-02 | 70 | paid
Модель daily_paid: отфильтровать paid, сгруппировать по day, посчитать sum(amount).
Эталон результата:
day | paid_amount
2026-01-01 | 100
2026-01-02 | 70
Проверки: order_id unique и not_null на orders; accepted_values status из {paid, refund}; day unique и not_null на daily_paid.
Контрольный дефект: дубликат order_id=1. Ожидаемое поведение — тест unique завершается FAIL, даже если SQL модели выполнился успешно.
Пошаговый порядок действий:
- Создайте проект с .yml-файлом конфигурации и директорией models.
- Создайте upstream-модель
models/orders.sqlс тремя учебными строками:
select 1 as order_id, cast('2026-01-01' as date) as day, 100 as amount, 'paid' as status
union all
select 2, cast('2026-01-01' as date), 40, 'refund'
union all
select 3, cast('2026-01-02' as date), 70, 'paid'
Эта модель делает ref('orders') ссылкой на ресурс проекта. Для реальных данных замените константы на select из исходной таблицы вашего хранилища.
3. Создайте файл models/daily_paid.sql с select и ref(‘orders’).
4. Задайте материализацию через config в начале файла.
5. Опишите проверки в models/schema.yml.
6. В настроенном dbt-проекте выполните dbt build, чтобы собрать модели и запустить проверки.
7. Внесите дубликат order_id=1 и убедитесь, что тест unique падает.
Что этот контракт проверяет. Он отделяет три вещи: корректность SQL, корректность зависимостей и корректность данных. SQL может выполниться, зависимости могут разрешиться, а данные при этом нарушают утверждение. Тест ловит именно третий случай.
Чего контракт не проверяет. Успешная сборка не подтверждает бизнес-определение выручки. Если в постановке задачи paid означает одно, а в коде фильтр стоит по другому статусу, тесты этого не заметят. Смысловые определения остаются за командой.
Версии и ограничения
Документация проекта на docs.getdbt.com описывает две мажорные версии. v2 — текущее поколение на Rust, оно ставится по умолчанию и приносит расширенный инструментарий разработчика, линтинг и понимание SQL. v1 — исходное поколение на Python, которое продолжает поддерживаться. Движок v2 написан на Rust с нативным пониманием SQL в разных диалектах, что позволяет ловить ошибки до отправки в хранилище и даёт автодополнение и inline-ошибки в редакторе.
Способы запуска. Документация перечисляет: dbt platform — рекомендуемый путь с планированием, CI/CD, хостингом документации, мониторингом и алертингом; локальный запуск из командной строки или редактора; dbt OSS — бесплатная полностью открытая дистрибуция на Apache 2.0 для организаций со строгим требованием к OSS-рантайму. Расширение для VS Code доступно и в v2, и в v1.
Ограничения, которые важно держать в голове:
- dbt работает со слоем преобразования. Исходные данные приходят из вашего ingestion-слоя.
- Модель описывает логику на SQL или Python. Python-модели поддерживаются начиная с версии 1.3.
- Тест проверяет утверждение о данных. Он не проверяет соответствие утверждения бизнес-смыслу.
- Успешный run означает, что SQL выполнился. Качество данных подтверждают тесты.
- Версии и команды стоит сверять с текущей документацией проекта: поведение v1 и v2 различается.
Частые вопросы
Что такое dbt data build tool простыми словами?
dbt — инструмент преобразования данных внутри хранилища. Вы пишете SQL select-запросы, описываете зависимости через ref и навешиваете проверки. Инструмент сам создаёт объекты в хранилище, следит за порядком выполнения и запускает тесты. Это слой T в ELT, который работает поверх уже загруженных данных.
Чем ref отличается от обычного имени таблицы?
ref(‘orders’) подставляет актуальное имя таблицы в текущем окружении и одновременно регистрирует зависимость между моделями. Благодаря этому dbt строит граф и выполняет модели в правильном порядке. Строковое имя таблицы такой связи не создаёт.
Какую материализацию выбрать для модели?
Для небольших результатов подходит view: данные всегда свежие, хранение не требуется. Для крупных таблиц фактов имеет смысл table или incremental. Инкрементальную модель настраивают отдельно; документация называет её способом оптимизации длительных сборок.
Чем data test отличается от unit test?
Data test — утверждение о данных в модели, например уникальность колонки. Он возвращает строки-нарушители, и ноль строк означает PASS. Unit test проверяет логику модели на заданных входных строках до материализации. В dbt build unit-тесты идут первыми, затем материализация, затем data tests.
Почему тест падает, хотя SQL выполнился?
Тест и запрос проверяют разное. SQL модели daily_paid группирует по дню и не замечает дубликат order_id. Тест unique на order_id выбирает дубликаты отдельным запросом и находит нарушение. Поэтому успешный run и пройденные тесты — два разных сигнала о состоянии данных.
