Стадия: Подготовка наших атомарных строительных блоков
Стадия — это место, где начинается наше путешествие. Это основа нашего проекта, где мы собираем все отдельные компоненты, которые будем использовать для создания более сложных и полезных моделей в проекте.
Мы будем использовать аналогию для работы с dbt на протяжении этого руководства: думать модульно в терминах атомов, молекул и более сложных выходов, таких как белки или клетки (мы заранее извиняемся перед химиками или биологами за наше неизбежное растягивание этой метафоры). В рамках этой концепции, если данные нашей исходной системы — это суп из сырой энергии и кварков, то вы можете думать о стадии как о конденсации и очистке этого материала в отдельные атомы, с которыми мы позже построим более сложные и полезные структуры.
Стадия: Файлы и папки
Давайте увеличим масштаб каталога стадии из нашего дерева файлов models в обзоре и пройдемся по тому, что здесь происходит.
models/staging
├── jaffle_shop
│ ├── _jaffle_shop__docs.md
│ ├── _jaffle_shop__models.yml
│ ├── _jaffle_shop__sources.yml
│ ├── base
│ │ ├── base_jaffle_shop__customers.sql
│ │ └── base_jaffle_shop__deleted_customers.sql
│ ├── stg_jaffle_shop__customers.sql
│ └── stg_jaffle_shop__orders.sql
└── stripe
├── _stripe__models.yml
├── _stripe__sources.yml
└── stg_stripe__payments.sql
- Папки (Folders). Структура папок в dbt чрезвычайно важна. Нам нужна согласованная структура не только для того, чтобы ориентироваться в кодовой базе, как в любом программном проекте, но и потому, что структура папок является одним из ключевых интерфейсов для понимания графа знаний, закодированного в нашем проекте (наряду с DAG и данными, загруженными в хранилище). Она должна отражать поток данных — шаг за шагом — от большого количества моделей, приведённых к структуре источников, к меньшему числу более насыщенных моделей, приведённых к бизнес‑структуре. Кроме того, структуру папок можно использовать как средство выбора в dbt с помощью selector syntax. Например, при описанной выше структуре, если мы загрузили свежие данные из Stripe и хотим запустить все модели, которые строятся на этих данных, мы можем просто выполнить
dbt build --select staging.stripe+— и всё готово для построения более актуальных отчётов по платежам.- ✅ Подкаталоги по исходной системе (source system). Наша внутренняя транзакционная база данных — это одна система, данные из API Stripe — другая, а события из инструмента Snowplow — третья. Мы считаем, что для большинства компаний это лучшая логика группировки, так как таблицы из одного источника обычно имеют схожие способы загрузки и свойства, что позволяет удобно работать с такими наборами данных.
- ❌ Подкаталоги по загрузчику (loader). Некоторые пытаются группировать данные по способу загрузки (Fivetran, Stitch, собственные синки), но для проекта любого заметного размера такая группировка оказывается слишком общей и мало полезной.
- ❌ Подкаталоги по бизнес‑группам. Другой подход, который мы не рекомендуем, — делить staging‑слой по бизнес‑направлениям и создавать подкаталоги вроде
marketing,financeи т. д. Одна из ключевых целей хорошего dbt‑проекта — создание единого источника истины. Разделяя данные слишком рано, мы рискуем получить дублирование и конфликтующие определения (например, разные базовые таблицы заказов у маркетинга и финансов). Мы хотим, чтобы все строили модели на одном и том же наборе «атомов», поэтому, по нашему опыту, начинать трансформации со staging‑структуры, отражающей структуру исходных систем, — это оптимальный уровень группировки на данном этапе.
- Имена файлов (File names). Создание согласованного шаблона именования файлов критически важно в dbt. Имена файлов должны быть уникальными и соответствовать имени модели при выборе и создании её в хранилище. Мы рекомендуем включать в имя файла как можно больше понятной информации: префикс слоя, в котором находится модель, важную информацию о группировке, а также конкретные сведения о сущности или трансформации, описываемой в модели.
- ✅
stg_[source]__[entity]s.sql— двойное подчёркивание между системой‑источником и сущностью помогает визуально разделить эти части, особенно если имя источника состоит из нескольких слов. Например,google_analytics__campaignsвсегда однозначно читается, тогда как для человека без контекстаgoogle_analytics_campaignsможет быть какanalytics_campaignsиз системы‑источникаgoogle, так иcampaignsиз системы‑источникаgoogle_analytics. Думайте об этом как об оксфордской запятой: дополнительная ясность полностью оправдывает лишний символ. - ❌
stg_[entity].sql— поначалу может казаться достаточно конкретным, но со временем перестаёт работать. Добавление системы‑источника в имя файла улучшает обнаруживаемость и позволяет понять происхождение модели даже без просмотра дерева файлов. - ✅ Множественное число (Plural). SQL, и особенно SQL в dbt, должен по возможности читаться как связный текст. Мы стремимся использовать ясность и декларативную природу SQL. Поэтому, если только в таблице
ordersне содержится один‑единственный заказ, использование множественного числа — корректный способ описать таблицу с несколькими строками.
- ✅
Стадия: Модели
Теперь, когда мы поняли, как файлы и папки сочетаются друг с другом, давайте заглянем внутрь одного из этих файлов и разберемся, что делает модель стадии хорошо структурированной.
Ниже приведён пример стандартной staging-модели (из нашей модели stg_stripe__payments), который иллюстрирует распространённые паттерны, используемые в слое staging. Мы организовали модель в два CTEs: один отвечает за подключение исходной таблицы через source macro, а второй — за применение наших преобразований.
Хотя наши последующие слои преобразований будут значительно различаться от модели к модели, каждая из наших моделей стадии будет следовать этому точному шаблону. Таким образом, мы должны убедиться, что установленный нами шаблон является надежным и последовательным.
-- stg_stripe__payments.sql
with
source as (
select * from {{ source('stripe','payment') }}
),
renamed as (
select
-- ids
id as payment_id,
orderid as order_id,
-- strings
paymentmethod as payment_method,
case
when payment_method in ('stripe', 'paypal', 'credit_card', 'gift_card') then 'credit'
else 'cash'
end as payment_type,
status,
-- numerics
amount as amount_cents,
amount / 100.0 as amount,
-- booleans
case
when status = 'successful' then true
else false
end as is_completed_payment,
-- dates
date_trunc('day', created) as created_date,
-- timestamps
created::timestamp_ltz as created_at
from source
)
select * from renamed
-
Основываясь на вышеизложенном, наиболее стандартные типы преобразований моделей стадии:
- ✅ Переименование
- ✅ Приведение типов
- ✅ Базовые вычисления (например, центы в доллары)
- ✅ Категоризация (использование условной логики для группировки значений в категории или булевы значения, как в операторах
case whenвыше) - ❌ Соединения — цель моделей стадии — очистить и подготовить отдельные концепции, соответствующие источнику, для дальнейшего использования. Мы создаем наиболее полезную версию таблицы системы источника, которую мы можем использовать как новый модульный компонент для нашего проекта. По нашему опыту, соединения почти всегда плохая идея здесь — они создают немедленное дублирование вычислений и запутанные отношения, которые распространяются вниз по потоку — хотя иногда бывают исключения (обратитесь к базовым моделям для получения дополнительной информации).
- ❌ Агрегации — агрегации подразумевают группировку, и мы не делаем этого на этом этапе. Помните — модели стадии — это ваше место для создания строительных блоков, которые вы будете использовать на протяжении всего остального проекта — если мы начнем изменять зернистость наших таблиц, группируя на этом слое, мы потеряем доступ к исходным данным, которые нам, вероятно, понадобятся в какой-то момент. Мы просто хотим, чтобы наши отдельные концепции были очищены и готовы к использованию, и будем обрабатывать агрегацию значений дальше по потоку.
-
✅ Материализованы как представления. Посмотрев на частичное представление нашего
dbt_project.ymlниже, мы можем увидеть, что мы настроили весь каталог стадии для материализации как представления. Поскольку они не предназначены для того, чтобы быть конечными артефактами сами по себе, а скорее строительными блоками для последующих моделей, модели стадии обычно должны быть материализованы как представления по двум ключевым причинам:-
Любая последующая модель (обсуждается подробнее в marts), ссылающаяся на наши модели стадии, всегда будет получать самые свежие данные из всех компонентных представлений, которые она объединяет и материализует
-
Это позволяет избежать траты места в хранилище на модели, которые не предназначены для запросов потребителями данных, и, следовательно, не нуждаются в быстром или эффективном выполнении
# dbt_project.yml
models:
jaffle_shop:
staging:
+materialized: view
-
-
Модели стадии — это единственное место, где мы будем использовать
sourceмакрос, и наши модели стадии должны иметь отношение 1-к-1 к нашим таблицам источников. Это означает, что для каждой таблицы системы источника у нас будет одна модель стадии, ссылающаяся на нее, действующая как ее точка входа — стадия — для использования дальше по потоку.
Модели стадии помогают нам держать наш код DRY. Модульная, повторно используемая структура dbt означает, что мы можем и должны продвигать любые преобразования, которые мы всегда будем хотеть использовать для данной компонентной модели, как можно дальше вверх по потоку. Это спасает нас от потенциальной траты кода, сложности и вычислений, выполняя одно и то же преобразование более одного раза. Например, если мы знаем, что всегда хотим, чтобы наши денежные значения были в виде чисел с плавающей запятой в долларах, но система источника — это целые числа и центы, мы хотим выполнить деление и приведение типов как можно раньше, чтобы мы могли ссылаться на это, а не повторять это снова и снова дальше по потоку.
Это приятное изменение для многих из нас, кто привык применять одни и те же наборы SQL-преобразований во многих местах из-за необходимости! Для нас, самая ранняя точка для этих 'всегда-желаемых' преобразований — это слой стадии, начальная точка входа в наш процесс преобразования. Принцип DRY в конечном итоге является лакмусовой бумажкой для того, должны ли преобразования происходить на слое стадии. Если мы захотим их в каждой последующей модели и они помогут нам устранить повторяющийся код, они, вероятно, в порядке.
Стадия: Другие соображения
-
Базовые модели, когда соединения необходимы для стадии концепций. Иногда, чтобы поддерживать чистый и DRY слой стадии, нам действительно нужно реализовать некоторые соединения, чтобы создать надежную концепцию для наших строительных блоков. В этих случаях мы рекомендуем создать подкаталог в каталоге стадии для рассматриваемой системы источника и строить
baseмодели. Эти модели имеют все те же свойства, которые обычно находятся в слое стадии, они будут напрямую использовать сырые данные и выполнять не-соединительные преобразования, затем в моделях стадии мы будем соединять необходимые базовые модели. Наиболее распространенные случаи использования для создания базового слоя под папкой стадии:-
✅ Соединение отдельных таблиц удаления. Иногда система источника может хранить удаления в отдельной таблице. Обычно мы хотим убедиться, что можем пометить или отфильтровать удаленные записи для всех наших компонентных моделей, поэтому нам нужно будет соединить эти записи удаления с любыми из наших объектов, которые следуют этому шаблону. Это пример, показанный ниже для иллюстрации.
-- base_jaffle_shop__customers.sql
with
source as (
select * from {{ source('jaffle_shop','customers') }}
),
customers as (
select
id as customer_id,
first_name,
last_name
from source
)
select * from customers-- base_jaffle_shop__deleted_customers.sql
with
source as (
select * from {{ source('jaffle_shop','customer_deletes') }}
),
deleted_customers as (
select
id as customer_id,
deleted as deleted_at
from source
)
select * from deleted_customers-- stg_jaffle_shop__customers.sql
with
customers as (
select * from {{ ref('base_jaffle_shop__customers') }}
),
deleted_customers as (
select * from {{ ref('base_jaffle_shop__deleted_customers') }}
),
join_and_mark_deleted_customers as (
select
customers.*,
case
when deleted_customers.deleted_at is not null then true
else false
end as is_deleted
from customers
left join deleted_customers on customers.customer_id = deleted_customers.customer_id
)
select * from join_and_mark_deleted_customers -
✅ Объединение разрозненных, но симметричных источников. Типичный пример здесь — если вы управляете несколькими платформами электронной коммерции в различных территориях через SaaS-платформу, такую как Shopify. У вас будут совершенно идентичные схемы, но все загружены отдельно в ваше хранилище. В этом случае легче рассуждать о наших заказах, если все наши магазины объединены вместе, поэтому мы хотели бы обработать объединение в базовой модели, прежде чем продолжить наши обычные преобразования модели стадии на (теперь полном) наборе — вы можете углубиться в более подробную информацию об этом случае использования здесь.
-
-
Codegen для автоматизации генерации таблиц стадии. Очень полезно научиться писать модели стадии вручную, они просты и многочисленны, поэтому могут быть отличным способом усвоить стиль написания SQL в dbt. Также, мы неизбежно найдем себя в необходимости добавления специальных элементов в конкретные модели время от времени — например, в одной из ситуаций выше, требующих базовых моделей — поэтому полезно глубоко понять, как они работают. Однако, как только это понимание установлено, поскольку модели стадии в значительной степени строятся, следуя одним и тем же шаблонам, и должны быть построены 1-к-1 для каждой таблицы источника в системе источника, предпочтительно начать автоматизировать их создание. Для этого у нас есть пакет codegen. Это позволит вам автоматически генерировать весь исходный YAML и шаблоны моделей стадии, чтобы ускорить этот шаг, и мы рекомендуем использовать его в каждом проекте.
-
Каталог утилит. Хотя это не в папке
staging, полезно рассматривать это как часть наших фундаментальных строительных блоков. Каталогmodels/utilities— это место, где мы можем хранить любые модели общего назначения, которые мы генерируем из макросов или на основе семян, которые предоставляют инструменты, помогающие нам выполнять наше моделирование, а не данные для моделирования. Наиболее распространенный случай использования — это хребет дат, сгенерированный с помощью пакета dbt utils.
Это руководство следует порядку DAG, чтобы мы могли получить целостное представление о том, как эти три основных слоя строятся друг на друге для создания значимых продуктов данных. Важно отметить, что разработка моделей обычно не движется линейно через DAG. Чаще всего, мы должны начать с моделирования дизайна в электронной таблице, чтобы убедиться, что мы согласованы с нашими заинтересованными сторонами по целям вывода. Затем мы напишем SQL для генерации этого вывода и определим, какие таблицы задействованы. Как только у нас есть наша логика и зависимости, мы убедимся, что мы подготовили все необходимые атомарные части в проект, затем объединим их на основе логики, которую мы написали, чтобы создать наш mart. Наконец, с функционирующей моделью, работающей в dbt, мы можем начать рефакторинг и оптимизацию этого mart. Разделяя логику и перемещая части обратно вверх по потоку в промежуточные модели, мы обеспечиваем, что все наши модели чисты и читаемы, история нашего DAG ясна, и у нас больше возможностей для применения тщательного тестирования.