Раздел 26 · Data Engineering

Jinja: шаблонизатор поверх SQL

junior~15 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Модели «Гонца» разложены по слоям, и Отто просит новую витрину: сколько заказов в каждой зоне доставлено и сколько отменено. На каждый статус по колонке. Пишешь первую строку count(*) filter (where status = 'delivered') as delivered_orders, копируешь, меняешь слово. Для двух статусов терпимо. А завтра у «Гонца» появятся статусы «возвращён» и «потерян», и каждую копию придётся найти и дописать во всех витринах. Повторяющийся SQL хочется не писать, а генерировать.

Бланк письма

В бухгалтерии есть бланк: «Уважаемый ___, напоминаем, что за ___ у вас задолженность ___ рублей». Бланк один, а писем сотни: в пропуски подставляют имя, месяц и сумму. Никто не перепечатывает письмо целиком для каждого клиента.

Модель dbt тоже бланк. Ты пишешь SQL с пропусками и правилами, а dbt перед запуском заполняет пропуски и отдаёт базе готовый запрос.

Что такое Jinja

Jinja это шаблонизатор, язык таких бланков. Ты им уже пользовался: {{ ref('stg_orders') }} и {% if is_incremental() %} это Jinja. Перед запуском dbt компилирует модель: исполняет всё в фигурных скобках и оставляет чистый SQL. База Jinja никогда не видит.

Скобок три вида:

  • {{ ... }} вставляет значение в текст: имя таблицы, число, строку;
  • {% ... %} это команда: условие, цикл, переменная. Сама по себе в текст ничего не печатает;
  • {# ... #} комментарий шаблона, в скомпилированный SQL он не попадёт.

Переменные и циклы

Переменную заводят командой set:

{% set statuses = ['delivered', 'cancelled'] %}

Квадратные скобки это список, как в Python. По списку можно пройти циклом for, и текст внутри повторится для каждого элемента:

select
    zone_id,
    {% for status in statuses %}
    count(*) filter (where status = '{{ status }}') as {{ status }}_orders{{ "," if not loop.last }}
    {% endfor %}
from {{ ref('stg_orders') }}
group by zone_id

На каждом шаге вместо {{ status }} подставляется очередной статус: и внутри кавычек, и в имени колонки.

Отдельная забота: запятые. Между колонками запятая нужна, а после последней SQL её не простит. Внутри цикла есть служебная переменная loop, и loop.last истинно на последнем шаге. Выражение {{ "," if not loop.last }} печатает запятую везде, кроме последней строки.

Появится новый статус, ты допишешь его в список, и колонка возникнет сама.

урок из курса

Урок из курса «Data Engineering». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.