Опоздание в минутах «Гонец» считает уже в трёх местах: в int_deliveries, в отчёте по курьерам и в черновике витрины для бухгалтерии. Везде одна и та же длинная формула round(extract(epoch from (delivered_at - promised_at)) / 60). Вчера Отто попросил считать опоздание от времени забора, а не от обещанного. Ты поправил два места из трёх, и цифры на дашборде разъехались. Пора вынести формулу туда, где её правят один раз.
Рецепт на холодильнике
Бабушкин рецепт пирога висит на холодильнике. Когда пекут пирог, его не переписывают заново, а говорят «по бабушкиному рецепту, только вдвое больше». Поменяла бабушка сахар на мёд, и все следующие пироги сразу с мёдом.
В прошлом уроке Jinja уже генерировала SQL циклом внутри одной модели. Рецепт на холодильнике это следующий шаг: кусок SQL с именем, который зовут из любой модели.
Макрос это функция, которая возвращает SQL
Макрос в dbt это функция на Jinja. Она принимает аргументы и возвращает кусок текста, который подставляется в модель при компиляции. Макросы живут в папке macros/:
-- macros/minutes_between.sql
{% macro minutes_between(start_col, end_col) -%}
round(extract(epoch from ({{ end_col }} - {{ start_col }})) / 60)
{%- endmacro %}
Внутри {% macro %} обычный шаблон: аргументы подставляются через {{ }}. Минусы у тегов -%} и {%- съедают пробелы и переводы строк вокруг, чтобы в модель не попали пустые строки. Вызов выглядит как вызов функции:
-- models/intermediate/int_deliveries.sql
select
order_id,
{{ minutes_between('promised_at', 'delivered_at') }} as delay_min
from {{ ref('stg_orders') }}
Обрати внимание на кавычки: 'promised_at' это строка, имя колонки. Макрос не знает про данные, он склеивает текст. После компиляции на месте вызова стоит та самая длинная формула, и база получает обычный SQL.
Теперь просьба Отто правится в одном файле. Поменял макрос, и все модели, которые его зовут, при следующем запуске считают по-новому.
Чужие рецепты: пакеты
Многие макросы нужны всем: суррогатный ключ из нескольких колонок, «все колонки, кроме двух», проверка диапазона значений. Писать их в каждом проекте незачем. Их собирают в пакеты, как библиотеки в программировании. Самый известный, dbt_utils, поддерживает сама dbt Labs.
Пакеты перечисляют в файле packages.yml в корне проекта:
packages:
- package: dbt-labs/dbt_utils
version: 1.4.1
Ставит их команда dbt deps: скачивает код пакета в папку dbt_packages/. Пока пакет не поставлен, dbt отказывается собирать проект и прямо об этом говорит: в packages.yml один пакет, а в dbt_packages ноль, запусти dbt deps. Папку dbt_packages/ в git не кладут, её создаёт dbt deps на каждой машине.
Макросы пакета зовут через его имя: dbt_utils.<макрос>. Так два пакета с макросом одного имени друг другу не мешают.
урок из курса
Урок из курса «Data Engineering». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.