target.name: conditional logic в Jinja per environment
dbt-проекты редко живут в одном environment. Типично: developer работает локально (dev), CI как окружение запускает dbt (ci) — механика CI/CD в модулях 12-13, scheduled runs в production (prod). У каждого environment могут быть свои отличия: разная схема (dev -> personal, prod -> clean), разная sample size (dev -> 1000 rows, prod -> all), разная materialization config (dev -> view для быстрого reload, prod -> table для performance).
dbt-core предоставляет target object — runtime context Jinja с info про текущий environment. target.name, target.database, target.schema, target.threads — это data, доступные в SQL моделях, YAML configs, macros. Через {% if target.name == 'prod' %} можно реализовать conditional logic.
Этот урок — про target object, common patterns multi-env, и антипаттерны hard-coded values.
dbt-iii: parse vs execute — когда Jinja резолвит targetЧто такое target
target — это runtime info про currently executing dbt run. Defined в profiles.yml:
# ~/.dbt/profiles.yml
my_project:
target: dev # default target
outputs:
dev:
type: duckdb
path: './analytics_dev.duckdb'
schema: 'dbt_levo_dev'
threads: 4
ci:
type: duckdb
path: ':memory:'
schema: 'main'
threads: 4
prod:
type: duckdb
path: '/data/analytics_prod.duckdb'
schema: 'analytics'
threads: 16
При запуске:
dbt run # target = dev (from default)
dbt run --target ci # target = ci
dbt run --target prod # target = prod
В Jinja доступен target object с полями:
| Поле | Значение |
|---|---|
target.name | Имя target (dev/ci/prod) |
target.type | Adapter type (duckdb/snowflake/postgres) |
target.database | Имя database |
target.schema | Schema (custom_schema если override) |
target.threads | Concurrent threads |
target.user | Username (per profile) |
target.host | Host (для networked DBs) |
target.port | Port |
Также adapter-specific поля (например, для Snowflake target.warehouse, target.role).
Базовое использование
Pattern 1: conditional filter
В dev — limit rows для быстрого dev cycle, в prod — full data:
SELECT * FROM {{ ref('large_source') }}
{% if target.name == 'dev' %}
LIMIT 1000 -- dev: sample
{% endif %}
dbt run локально:
LIMIT 1000 applied
dbt run --target prod:
No LIMIT (full data)
Pattern 2: conditional materialization
В dev — view (быстрый reload), в prod — table (performance):
- name: customer_metrics
config:
materialized: "{{ 'table' if target.name == 'prod' else 'view' }}"
Pattern 3: conditional sources
{% set source_table = 'raw.orders_full' if target.name == 'prod' else 'raw.orders_sample' %}
SELECT * FROM {{ source_table }}
(Лучше через source(), но иногда нужно как пример.)
Pattern 4: conditional config через macro
- name: customer_metrics
config:
persist_docs:
relation: "{{ target.name == 'prod' }}"
columns: "{{ target.name == 'prod' }}"
В prod — persist docs (BI tools видят), в dev — нет (overhead).
target в SQL моделях
-- models/marts/customer_metrics.sql
SELECT
c.customer_id,
c.first_name,
SUM(o.order_total) AS lifetime_value
FROM {{ ref('stg_customers') }} c
LEFT JOIN {{ ref('stg_orders') }} o ON o.customer_id = c.customer_id
{% if target.name == 'dev' %}
WHERE o.order_date >= CURRENT_DATE - INTERVAL '30 day' -- dev: только recent
{% endif %}
GROUP BY c.customer_id, c.first_name
В dev fast (30 days of data), в prod full history.
target в YAML configs
# dbt_project.yml
models:
my_project:
+materialized: "{{ 'table' if target.name == 'prod' else 'view' }}"
marts:
+persist_docs:
relation: "{{ target.name == 'prod' }}"
columns: "{{ target.name == 'prod' }}"
+schema: "marts"
Конфигурация условно applied per env.
Common patterns multi-env
Полный пример: проект с 3 targets
# profiles.yml
my_project:
target: dev
outputs:
dev:
type: duckdb
path: './dev.duckdb'
schema: "dbt_{{ env_var('USER', 'unknown') }}_dev"
threads: 4
ci:
type: duckdb
path: ':memory:'
schema: 'ci'
threads: 4
prod:
type: duckdb
path: '/data/prod.duckdb'
schema: 'analytics'
threads: 16
dbt_project.yml:
name: my_project
version: '1.0.0'
vars:
# default vars
reference_date: "{{ run_started_at.date() }}"
sample_size: 1000
models:
my_project:
# default: table в prod, view везде
+materialized: "{{ 'table' if target.name == 'prod' else 'view' }}"
staging:
+schema: stg
+persist_docs:
relation: false # staging — no persist anywhere
marts:
+schema: marts
+materialized: "{{ 'table' if target.name == 'prod' else 'view' }}"
+persist_docs:
relation: "{{ target.name == 'prod' }}"
columns: "{{ target.name == 'prod' }}"
tests:
# warn в dev, error в prod
+severity: "{{ 'error' if target.name in ('prod', 'ci') else 'warn' }}"
Use:
dbt run # dev: views, no persist, warn tests
dbt run --target ci # ci: in-memory, no persist, error tests
dbt run --target prod # prod: tables, persist docs, error tests
Антипаттерны target
1. Hard-coded target.name
-- ПЛОХО: тест зависит от dev
WHERE order_date >= CURRENT_DATE - INTERVAL '30 day' -- dev только
Без condition — продакшен фильтрует 30 days. Maybe wrong.
-- ХОРОШО: explicit conditional
{% if target.name == 'dev' %}
WHERE order_date >= CURRENT_DATE - INTERVAL '30 day'
{% endif %}
2. target.name в data tests
# ПЛОХО
data_tests:
- dbt_utils.expression_is_true:
expression: "{% if target.name == 'dev' %} count > 100 {% else %} count > 10000 {% endif %}"
Сложная логика, hard to debug. Better — use vars или separate tests per env.
3. Too many target conditions
{% if target.name == 'dev' %}
...
{% elif target.name == 'ci' %}
...
{% elif target.name == 'staging' %}
...
{% elif target.name == 'prod' %}
...
{% endif %}
10 такого + код становится unreadable. Refactor — extract в macro:
{{ get_sample_filter() }}
{% macro get_sample_filter() %}
{% if target.name in ('dev', 'ci') %}
WHERE date >= CURRENT_DATE - INTERVAL '30 day'
{% elif target.name == 'staging' %}
WHERE date >= CURRENT_DATE - INTERVAL '90 day'
{% else %}
-- prod: no filter
{% endif %}
{% endmacro %}
4. target.name для secrets
-- ПЛОХО — secrets в SQL
{% if target.name == 'prod' %}
WHERE api_key = 'sk-prod-12345'
{% endif %}
Secrets — через env_vars (следующий урок), не conditional на target.
5. Forgotten dev-only conditions в commit
Developer добавил WHERE order_date >= ... для testing, forgot to wrap in {% if target.name == 'dev' %}. Production runs с filter — broken data.
Solution — code review + CI test что prod target gives expected results.
6. target в run-only macros
Some macros (run_query, statement) execute только на execute=True mode. Don’t rely on target.name выбор в parse-time logic without checking execute mode.
target patterns для CI
CI как окружение использует свой target: ci. Подробная механика CI пайплайнов, jobs, workflow — в модулях 12-ci-cd-local и 13-ci-cd-github. Здесь важно: CI target в profiles.yml существует наравне с dev/prod, и тот же conditional logic (target.name == 'ci') применим.
env_var в target
В profiles.yml можно использовать env_var для гибкости:
prod:
type: duckdb
path: "{{ env_var('DBT_PROD_DB_PATH', '/data/prod.duckdb') }}"
threads: "{{ env_var('DBT_PROD_THREADS', 16) | int }}"
Это позволяет same profiles.yml для разных deploys (staging vs prod), различающихся через env vars. Об этом — следующий урок.
Тестирование conditional logic
Локально:
# Test dev behavior
dbt run --target dev --select customer_metrics
dbt show --select customer_metrics --limit 10
# Test prod behavior
dbt run --target prod --select customer_metrics
dbt show --select customer_metrics --limit 10
Проверить, что:
- Dev filters work
- Prod gets all data
- No surprises после deploy
(Запуск нескольких targets в CI пайплайне — см. модуль 13-ci-cd-github.)
Попробуй сам
-
Setup profiles.yml с dev/ci/prod targets.
-
Add conditional:
- В одной модели:
LIMIT 1000для dev, full для prod - В config: persist_docs только prod
- В test: severity warn dev, error prod
- В одной модели:
-
Test:
dbt run --target dev dbt show --select my_model # verify limit dbt run --target prod dbt show --select my_model # verify no limit -
Try refactor if много conditions — extract в macro.
-
Anti-pattern check — найти любую модель с hard-coded value depend on environment. Refactor с target.name.
Ключевые выводы
- target object в Jinja — runtime info про текущий dbt run. Fields:
name,type,database,schema,threads,user, etc. - Conditional logic через
{% if target.name == 'prod' %}— для multi-env behavior (filters, materialization, persist_docs). - Common patterns: sample data в dev, materialization optimization (table prod / view dev), persist_docs only prod, test severity по env.
- dbt_project.yml configs могут условно зависеть от target —
materialized: "{{ 'table' if target.name == 'prod' else 'view' }}". - Anti-patterns: hard-coded conditions (без if wrap), too many target conditions (refactor в macro), secrets через target.name (use env_var), forgotten dev-only conditions в commit.
- Refactor много conditions в macro:
get_sample_filter()— DRY, testable. - CI should mirror prod — materialized tables, full tests, error severity. Differences: smaller data, ephemeral storage.
- Тестирование conditional —
dbt run --target Xна каждом target во время разработки + CI runs всех targets.